From 986ef14584629191692f754ff0aecca1f917afce Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Mon, 7 Jul 2025 18:37:52 -0400 Subject: [PATCH 01/21] Remove lingering code from delta encoding --- mvi/env.py | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/mvi/env.py b/mvi/env.py index 6c9de64..cf1f463 100644 --- a/mvi/env.py +++ b/mvi/env.py @@ -49,9 +49,6 @@ def __init__(self, config: ConnectionConfig): self.connected: bool = False self.logger: logging.Logger = logging.getLogger(__name__) - # Delta encoding state - self.current_frame: np.ndarray | None = None - def connect(self) -> bool: """ Establish connection to the Minecraft Forge mod @@ -68,7 +65,7 @@ def connect(self) -> bool: self.command_socket.settimeout(self.config.timeout) self.command_socket.connect(self.config.command_port) - # Create Unix domain socket for frame data with performance optimizations + # Create Unix domain socket for frame data self.data_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.data_socket.settimeout(self.config.timeout) @@ -254,7 +251,6 @@ def __init__( # This will be expanded when action support is added self.action_space = spaces.Discrete(1) # No-op action - self.current_frame = None self.step_count = 0 self.logger = logging.getLogger(__name__) @@ -287,18 +283,18 @@ def reset( self.client.send_command("RESET") # Get initial frame - self.current_frame = self.client.receive_frame_data() - if self.current_frame is None: + current_frame = self.client.receive_frame_data() + if current_frame is None: self.logger.warning("No frame data received") # Return a black frame if we can't get data height, width = self.connection_config.width, self.connection_config.height - self.current_frame = np.zeros((height, width, 3), dtype=np.uint8) + current_frame = np.zeros((height, width, 3), dtype=np.uint8) else: - self.logger.info(f"Received frame data: {self.current_frame.shape}") + self.logger.info(f"Received frame data: {current_frame.shape}") self.step_count = 0 - return self.current_frame, {"step_count": self.step_count} + return current_frame, {"step_count": self.step_count} def step(self, action) -> tuple[np.ndarray, float, bool, bool, dict[str, Any]]: """ @@ -319,12 +315,10 @@ def step(self, action) -> tuple[np.ndarray, float, bool, bool, dict[str, Any]]: # Get new frame data new_frame = self.client.receive_frame_data() if new_frame is not None: - self.current_frame = self._resize_frame(new_frame) - - # Ensure current_frame is never None - if self.current_frame is None: + current_frame = self._resize_frame(new_frame) + else: height, width = self.config.engine.image_size - self.current_frame = np.zeros((height, width, 3), dtype=np.uint8) + current_frame = np.zeros((height, width, 3), dtype=np.uint8) self.step_count += 1 @@ -335,26 +329,21 @@ def step(self, action) -> tuple[np.ndarray, float, bool, bool, dict[str, Any]]: info = {"step_count": self.step_count, "frame_received": new_frame is not None} - return self.current_frame, reward, terminated, truncated, info + return current_frame, reward, terminated, truncated, info def render(self, mode: str = "rgb_array") -> np.ndarray | None: """ - Render the environment - Parameters ---------- mode : str - Render mode (only "rgb_array" supported) + Render mode - Returns - ------- - np.ndarray | None - Rendered frame as numpy array + Raises + ------ + NotImplementedError + Rendering is not supported. """ - if mode != "rgb_array": - raise ValueError(f"Unsupported render mode: {mode}") - - return self.current_frame + raise NotImplementedError("Rendering is not supported.") def close(self): """Close the environment and disconnect from Minecraft""" From d0f031e07b1984417fde3ee3a0f9c3fe00a48a4a Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 12 Jul 2025 13:21:57 -0400 Subject: [PATCH 02/21] Added some communication structure --- .../main/java/com/mvi/mvimod/ActionState.java | 14 +++++++++ .../com/mvi/mvimod/ClientEventHandler.java | 30 +++++++++++++++---- .../main/java/com/mvi/mvimod/DataBridge.java | 15 ++-------- .../main/java/com/mvi/mvimod/Observation.java | 13 ++++++++ 4 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 forge/src/main/java/com/mvi/mvimod/ActionState.java create mode 100644 forge/src/main/java/com/mvi/mvimod/Observation.java diff --git a/forge/src/main/java/com/mvi/mvimod/ActionState.java b/forge/src/main/java/com/mvi/mvimod/ActionState.java new file mode 100644 index 0000000..58dc82d --- /dev/null +++ b/forge/src/main/java/com/mvi/mvimod/ActionState.java @@ -0,0 +1,14 @@ +package com.mvi.mvimod; + +public record ActionState( +) { + public byte[] serialize() { + // TODO: Implement this + throw new UnsupportedOperationException("Not implemented"); + } + + public static ActionState deserialize(byte[] data) { + // TODO: Implement this + throw new UnsupportedOperationException("Not implemented"); + } +} diff --git a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java index c58ff21..8f981ae 100644 --- a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java @@ -30,8 +30,18 @@ public static void onServerStopping(ServerStoppingEvent event) { @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { + ArrayList commands = dataBridge.emptyCommandQueue(); + for (String command : commands) { + processCommand(command); + + } + if (event.phase == TickEvent.Phase.END) { - captureAndSendFrame(); + // TODO: Move to data bridge? + int reward = packageReward(); + ActionState actionState = captureActionState(); + byte[] frame = captureFrame(); + dataBridge.sendObservation(new Observation(reward, actionState, frame)); } } @@ -49,14 +59,22 @@ public static void onPlayerDeath(LivingDeathEvent event) { } } - private static void captureAndSendFrame() { + private static int packageReward() { + // TODO: Get rewards from the reward queue + throw new UnsupportedOperationException("Not implemented"); + } + + private static ActionState captureActionState() { + // TODO: Get the state of every action the user can take + throw new UnsupportedOperationException("Not implemented"); + } + + private static byte[] captureFrame() { Minecraft mc = Minecraft.getInstance(); if (mc.level != null && mc.player != null) { - LOGGER.info("Capturing screenshot"); - byte[] frameData = captureScreenshot(mc.getWindow()); - LOGGER.info("Sending frame data"); - dataBridge.sendFrame(frameData); + return captureScreenshot(mc.getWindow()); } + return null; } private static byte[] captureScreenshot(Window window) { diff --git a/forge/src/main/java/com/mvi/mvimod/DataBridge.java b/forge/src/main/java/com/mvi/mvimod/DataBridge.java index 42718b1..0a1df65 100644 --- a/forge/src/main/java/com/mvi/mvimod/DataBridge.java +++ b/forge/src/main/java/com/mvi/mvimod/DataBridge.java @@ -21,19 +21,10 @@ public void setNetworkHandler(NetworkHandler handler) { LOGGER.info("NetworkHandler connected to DataBridge"); } - public void sendEvent(String eventType, String data) { + public void sendObservation(Observation obs) { if (networkHandler != null) { - LOGGER.info("Sending event: {} with data: {}", eventType, data); - // TODO: Implement this - } else { - LOGGER.warn("Cannot send event - NetworkHandler is null"); - } - } - - public void sendFrame(byte[] frameData) { - if (networkHandler != null) { - LOGGER.info("DataBridge sending frame data (size: {} bytes)", frameData.length); - networkHandler.setLatest(frameData, 0); + LOGGER.info("DataBridge sending frame data (size: {} bytes)", obs.frame().length); + networkHandler.setLatest(obs.frame(), obs.reward()); } else { LOGGER.warn("Cannot send frame - NetworkHandler is null"); } diff --git a/forge/src/main/java/com/mvi/mvimod/Observation.java b/forge/src/main/java/com/mvi/mvimod/Observation.java new file mode 100644 index 0000000..407dd86 --- /dev/null +++ b/forge/src/main/java/com/mvi/mvimod/Observation.java @@ -0,0 +1,13 @@ +package com.mvi.mvimod; + +public record Observation(int reward, ActionState actionState, byte[] frame) { + public byte[] serialize() { + // TODO: Implement this + throw new UnsupportedOperationException("Not implemented"); + } + + public static Observation deserialize(byte[] data) { + // TODO: Implement this + throw new UnsupportedOperationException("Not implemented"); + } +} From e187e27a791ea207abd6a02251b786333b600cfe Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sun, 27 Jul 2025 21:38:33 -0400 Subject: [PATCH 03/21] Network communication for actions; Started key mappings --- .../src/main/java/com/mvi/mvimod/Action.java | 160 ++++++++++++++++++ .../main/java/com/mvi/mvimod/ActionState.java | 104 +++++++++++- .../com/mvi/mvimod/ClientEventHandler.java | 159 ++++++++++++++++- .../main/java/com/mvi/mvimod/DataBridge.java | 13 ++ .../java/com/mvi/mvimod/NetworkHandler.java | 40 +++-- .../main/java/com/mvi/mvimod/Observation.java | 17 +- 6 files changed, 457 insertions(+), 36 deletions(-) create mode 100644 forge/src/main/java/com/mvi/mvimod/Action.java diff --git a/forge/src/main/java/com/mvi/mvimod/Action.java b/forge/src/main/java/com/mvi/mvimod/Action.java new file mode 100644 index 0000000..7433810 --- /dev/null +++ b/forge/src/main/java/com/mvi/mvimod/Action.java @@ -0,0 +1,160 @@ +package com.mvi.mvimod; + +import java.nio.ByteBuffer; + +/* + * This class represents the schema for communication between the client and server. + * It is a data structure that can pack and unpack the action state + * into a byte array and back. + * + * The format is as follows: + * - UP (1 bit) + * - DOWN (1 bit) + * - LEFT (1 bit) + * - RIGHT (1 bit) + * - JUMP (1 bit) + * - SNEAK (1 bit) + * - SPRINT (1 bit) + * - INVENTORY (1 bit) + * - DROP (1 bit) + * - SWAP (1 bit) + * - USE (1 bit) + * - ATTACK (1 bit) + * - PICK_ITEM (1 bit) + * - HOTBAR_1 (1 bit) + * - HOTBAR_2 (1 bit) + * - HOTBAR_3 (1 bit) + * - HOTBAR_4 (1 bit) + * - HOTBAR_5 (1 bit) + * - HOTBAR_6 (1 bit) + * - HOTBAR_7 (1 bit) + * - HOTBAR_8 (1 bit) + * - EXIT_MENU (1 bit) + * - PADDING (1 bit) + * - PADDING (1 bit) + * - Mouse control X (4 bytes) + * - Mouse control Y (4 bytes) + */ +public record Action( + boolean up, + boolean down, + boolean left, + boolean right, + boolean jump, + boolean sneak, + boolean sprint, + boolean inventory, + boolean drop, + boolean swap, + boolean use, + boolean attack, + boolean pick_item, + boolean hotbar1, + boolean hotbar2, + boolean hotbar3, + boolean hotbar4, + boolean hotbar5, + boolean hotbar6, + boolean hotbar7, + boolean hotbar8, + boolean exitMenu, + float mouseControlX, + float mouseControlY +) { + public byte[] toBytes() { + ByteBuffer buffer = ByteBuffer.allocate(3 + 4 + 4); + + int firstByte = 0; + firstByte |= (up ? 1 : 0); + firstByte <<= 1; + firstByte |= (down ? 1 : 0); + firstByte <<= 1; + firstByte |= (left ? 1 : 0); + firstByte <<= 1; + firstByte |= (right ? 1 : 0); + firstByte <<= 1; + firstByte |= (jump ? 1 : 0); + firstByte <<= 1; + firstByte |= (sneak ? 1 : 0); + firstByte <<= 1; + firstByte |= (sprint ? 1 : 0); + firstByte <<= 1; + firstByte |= (inventory ? 1 : 0); + buffer.put((byte) firstByte); + + int secondByte = 0; + secondByte |= (drop ? 1 : 0); + secondByte <<= 1; + secondByte |= (swap ? 1 : 0); + secondByte <<= 1; + secondByte |= (use ? 1 : 0); + secondByte <<= 1; + secondByte |= (attack ? 1 : 0); + secondByte <<= 1; + secondByte |= (pick_item ? 1 : 0); + secondByte <<= 1; + secondByte |= (hotbar1 ? 1 : 0); + secondByte <<= 1; + secondByte |= (hotbar2 ? 1 : 0); + secondByte <<= 1; + secondByte |= (hotbar3 ? 1 : 0); + buffer.put((byte) secondByte); + + int thirdByte = 0; + thirdByte |= (hotbar4 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar5 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar6 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar7 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar8 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (exitMenu ? 1 : 0); + buffer.put((byte) thirdByte); + + buffer.putFloat(mouseControlX); + buffer.putFloat(mouseControlY); + + return buffer.array(); + } + + public static Action fromBytes(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + int firstByte = buffer.get(); + int secondByte = buffer.get(); + int thirdByte = buffer.get(); + + float mouseControlX = buffer.getFloat(); + float mouseControlY = buffer.getFloat(); + + return new Action( + ((firstByte >> 0) & 1) == 1, + ((firstByte >> 1) & 1) == 1, + ((firstByte >> 2) & 1) == 1, + ((firstByte >> 3) & 1) == 1, + ((firstByte >> 4) & 1) == 1, + ((firstByte >> 5) & 1) == 1, + ((firstByte >> 6) & 1) == 1, + ((firstByte >> 7) & 1) == 1, + ((secondByte >> 0) & 1) == 1, + ((secondByte >> 1) & 1) == 1, + ((secondByte >> 2) & 1) == 1, + ((secondByte >> 3) & 1) == 1, + ((secondByte >> 4) & 1) == 1, + ((secondByte >> 5) & 1) == 1, + ((secondByte >> 6) & 1) == 1, + ((secondByte >> 7) & 1) == 1, + ((thirdByte >> 0) & 1) == 1, + ((thirdByte >> 1) & 1) == 1, + ((thirdByte >> 2) & 1) == 1, + ((thirdByte >> 3) & 1) == 1, + ((thirdByte >> 4) & 1) == 1, + ((thirdByte >> 5) & 1) == 1, + mouseControlX, + mouseControlY + ); + } +} diff --git a/forge/src/main/java/com/mvi/mvimod/ActionState.java b/forge/src/main/java/com/mvi/mvimod/ActionState.java index 58dc82d..2756c55 100644 --- a/forge/src/main/java/com/mvi/mvimod/ActionState.java +++ b/forge/src/main/java/com/mvi/mvimod/ActionState.java @@ -1,14 +1,100 @@ package com.mvi.mvimod; -public record ActionState( -) { - public byte[] serialize() { - // TODO: Implement this - throw new UnsupportedOperationException("Not implemented"); - } +import java.util.HashMap; +import java.util.Map; - public static ActionState deserialize(byte[] data) { - // TODO: Implement this - throw new UnsupportedOperationException("Not implemented"); +public class ActionState { + private final Map keyStates; + private float mouseDeltaX = 0.0f; + private float mouseDeltaY = 0.0f; + + public ActionState() { + this.keyStates = new HashMap<>(); + + // Initialize all tracked keys to false + initializeKeyStates(); + } + + private void initializeKeyStates() { + // Movement keys + keyStates.put("UP", false); + keyStates.put("DOWN", false); + keyStates.put("LEFT", false); + keyStates.put("RIGHT", false); + + // Action keys + keyStates.put("JUMP", false); // Jump + keyStates.put("SNEAK", false); // Sneak + keyStates.put("SPRINT", false); // Sprint + keyStates.put("INVENTORY", false); // Inventory + keyStates.put("DROP", false); // Drop + keyStates.put("CANCEL", false); // Menu/Cancel + keyStates.put("SWAP", false); // Swap off-hand + keyStates.put("USE", false); + keyStates.put("ATTACK", false); + keyStates.put("PICK_ITEM", false); + + // Menu control + keyStates.put("MENU_EXIT", false); // ESC key for exiting menus + + // Number keys for hotbar + for (int i = 1; i <= 9; i++) { + keyStates.put("HOTBAR_" +String.valueOf(i), false); + } + } + + public boolean isKeyPressed(String key) { + return keyStates.getOrDefault(key, false); + } + + public void setKeyPressed(String key, boolean pressed) { + keyStates.put(key, pressed); + } + + public Map getKeyStates() { + return new HashMap<>(keyStates); + } + + public void releaseAllKeys() { + for (String key : keyStates.keySet()) { + keyStates.put(key, false); } + // Reset mouse deltas when releasing all keys + mouseDeltaX = 0.0f; + mouseDeltaY = 0.0f; + } + + /** + * Set mouse movement delta for head/camera control + * @param deltaX Horizontal mouse movement (positive = right) + * @param deltaY Vertical mouse movement (positive = up) + */ + public void setMouseDelta(float deltaX, float deltaY) { + this.mouseDeltaX = deltaX; + this.mouseDeltaY = deltaY; + } + + /** + * Get horizontal mouse movement delta + * @return Mouse delta X value + */ + public float getMouseDeltaX() { + return mouseDeltaX; + } + + /** + * Get vertical mouse movement delta + * @return Mouse delta Y value + */ + public float getMouseDeltaY() { + return mouseDeltaY; + } + + /** + * Reset mouse deltas (typically called after processing movement) + */ + public void resetMouseDeltas() { + mouseDeltaX = 0.0f; + mouseDeltaY = 0.0f; + } } diff --git a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java index 8f981ae..c731f71 100644 --- a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java @@ -3,7 +3,11 @@ import com.mojang.blaze3d.platform.Window; import com.mojang.logging.LogUtils; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import net.minecraft.client.Minecraft; +import net.minecraft.client.KeyMapping; import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; @@ -17,10 +21,18 @@ public class ClientEventHandler { private static final Logger LOGGER = LogUtils.getLogger(); private static DataBridge dataBridge = DataBridge.getInstance(); + + // Track action state across ticks + private static ActionState actionState = new ActionState(); + + // Map string keys to Minecraft KeyMapping objects + private static final Map keyMappings = new HashMap<>(); + private static boolean keyMappingsInitialized = false; @SubscribeEvent public static void onServerStarting(ServerStartingEvent event) { LOGGER.info("MVI Mod Server Starting - Network handler is managed on client side"); + initializeKeyMappings(); } @SubscribeEvent @@ -30,18 +42,21 @@ public static void onServerStopping(ServerStoppingEvent event) { @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { + ArrayList commands = dataBridge.emptyCommandQueue(); for (String command : commands) { processCommand(command); - } if (event.phase == TickEvent.Phase.END) { + // Increment action state timings + actionState.incrementTimings(); + // TODO: Move to data bridge? int reward = packageReward(); - ActionState actionState = captureActionState(); + ActionState currentActionState = captureActionState(); byte[] frame = captureFrame(); - dataBridge.sendObservation(new Observation(reward, actionState, frame)); + dataBridge.sendObservation(new Observation(reward, currentActionState, frame)); } } @@ -58,15 +73,130 @@ public static void onPlayerDeath(LivingDeathEvent event) { dataBridge.sendEvent("PLAYER_DEATH", "-100.0"); } } + + private static void initializeKeyMappings() { + Minecraft mc = Minecraft.getInstance(); + if (mc.options == null) return; + + // Movement keys + keyMappings.put("UP", mc.options.keyUp); + keyMappings.put("DOWN", mc.options.keyDown); + keyMappings.put("LEFT", mc.options.keyLeft); + keyMappings.put("RIGHT", mc.options.keyRight); + + // Action keys + keyMappings.put("JUMP", mc.options.keyJump); + keyMappings.put("SNEAK", mc.options.keyShift); + keyMappings.put("SPRINT", mc.options.keySprint); + keyMappings.put("INVENTORY", mc.options.keyInventory); + keyMappings.put("DROP", mc.options.keyDrop); + keyMappings.put("SWAP", mc.options.keySwapOffhand); + keyMappings.put("USE", mc.options.keyUse); + keyMappings.put("ATTACK", mc.options.keyAttack); + keyMappings.put("PICK_ITEM", mc.options.keyPickItem); + + // Number keys for hotbar + KeyMapping[] hotbarKeys = mc.options.keyHotbarSlots; + for (int i = 0; i < hotbarKeys.length && i < 9; i++) { + keyMappings.put("HOTBAR_" + String.valueOf(i + 1), hotbarKeys[i]); + } + + keyMappingsInitialized = true; + LOGGER.info("Key mappings initialized with {} keys", keyMappings.size()); + } + + private static void processCommand(String command) { + LOGGER.debug("Processing command: {}", command); + + if (command.startsWith("PRESS_")) { + String keyName = command.substring(6); + pressKey(keyName); + } else if (command.startsWith("RELEASE_")) { + String keyName = command.substring(8); + releaseKey(keyName); + } else if (command.startsWith("MOUSE_MOVE_")) { + // Format: MOUSE_MOVE_X_Y (e.g., MOUSE_MOVE_5.0_-2.5) + String coords = command.substring(11); + String[] parts = coords.split("_"); + if (parts.length == 2) { + try { + float deltaX = Float.parseFloat(parts[0]); + float deltaY = Float.parseFloat(parts[1]); + moveMouseForHeadControl(deltaX, deltaY); + } catch (NumberFormatException e) { + LOGGER.warn("Invalid mouse movement coordinates: {}", coords); + } + } + } else if (command.equals("ESCAPE")) { + pressEscapeKey(); + } else { + LOGGER.warn("Unknown command format: {}", command); + } + } + + private static void pressKey(String keyName) { + KeyMapping keyMapping = keyMappings.get(keyName); + if (keyMapping != null) { + if (!keyMapping.isDown()) { + keyMapping.setDown(true); + actionState.setKeyPressed(keyName, true); + LOGGER.debug("Pressed key: {}", keyName); + } + } else { + LOGGER.warn("Unknown key: {}", keyName); + } + } + + private static void releaseKey(String keyName) { + KeyMapping keyMapping = keyMappings.get(keyName); + if (keyMapping != null) { + if (keyMapping.isDown()) { + keyMapping.setDown(false); + actionState.setKeyPressed(keyName, false); + LOGGER.debug("Released key: {}", keyName); + } + } else { + LOGGER.warn("Unknown key: {}", keyName); + } + } + + private static void moveMouseForHeadControl(float deltaX, float deltaY) { + Minecraft mc = Minecraft.getInstance(); + if (mc.player != null) { + // Store mouse deltas in action state + actionState.setMouseDelta(deltaX, deltaY); + + // Apply mouse movement to player rotation + // deltaX affects yaw (horizontal looking) + // deltaY affects pitch (vertical looking) + mc.player.turn(deltaX * 0.15, deltaY * 0.15); + + LOGGER.debug("Applied mouse movement: deltaX={}, deltaY={}", deltaX, deltaY); + } + } + + private static void pressEscapeKey() { + Minecraft mc = Minecraft.getInstance(); + if (mc.screen != null) { + // Close current screen/menu + mc.setScreen(null); + } + actionState.setKeyPressed("MENU_EXIT", true); + LOGGER.debug("Pressed ESC key - closing menu"); + } private static int packageReward() { // TODO: Get rewards from the reward queue - throw new UnsupportedOperationException("Not implemented"); + return 0; // Placeholder } private static ActionState captureActionState() { - // TODO: Get the state of every action the user can take - throw new UnsupportedOperationException("Not implemented"); + // Return a copy of the current action state + ActionState currentState = new ActionState(); + for (Map.Entry entry : actionState.getKeyStates().entrySet()) { + currentState.setKeyPressed(entry.getKey(), entry.getValue()); + } + return currentState; } private static byte[] captureFrame() { @@ -88,4 +218,21 @@ private static byte[] captureScreenshot(Window window) { buffer.get(bytes); return bytes; } + + /** + * Release all currently pressed keys (for cleanup on disconnect) + */ + public static void releaseAllKeys() { + if (!keyMappingsInitialized) return; + + LOGGER.info("Releasing all pressed keys for cleanup"); + for (Map.Entry entry : keyMappings.entrySet()) { + KeyMapping keyMapping = entry.getValue(); + if (keyMapping.isDown()) { + keyMapping.setDown(false); + LOGGER.debug("Released key during cleanup: {}", entry.getKey()); + } + } + actionState.releaseAllKeys(); + } } diff --git a/forge/src/main/java/com/mvi/mvimod/DataBridge.java b/forge/src/main/java/com/mvi/mvimod/DataBridge.java index 0a1df65..941f1d7 100644 --- a/forge/src/main/java/com/mvi/mvimod/DataBridge.java +++ b/forge/src/main/java/com/mvi/mvimod/DataBridge.java @@ -2,11 +2,16 @@ import com.mojang.logging.LogUtils; import org.slf4j.Logger; +import java.util.concurrent.atomic.AtomicReference; public class DataBridge { private static final Logger LOGGER = LogUtils.getLogger(); private static DataBridge instance; private NetworkHandler networkHandler; + + private final AtomicReference latestAction = new AtomicReference(); + + private DataBridge() {} public static DataBridge getInstance() { if (instance == null) { @@ -29,4 +34,12 @@ public void sendObservation(Observation obs) { LOGGER.warn("Cannot send frame - NetworkHandler is null"); } } + + public void setLatestAction(Action action) { + latestAction.set(action); + } + + public Action getLatestAction() { + return latestAction.get(); + } } diff --git a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java index 9903d7b..9b07de7 100644 --- a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java @@ -145,19 +145,30 @@ private void acceptReceiveClients() { private void handleReceiveClient(SocketChannel clientSocket) { receiverExecutor.submit( () -> { - try (BufferedReader in = - new BufferedReader( - new InputStreamReader(clientSocket.socket().getInputStream())); - PrintWriter out = new PrintWriter(clientSocket.socket().getOutputStream(), true)) { + try { + clientSocket.configureBlocking(true); + ByteBuffer actionBuffer = ByteBuffer.allocate(11); // Action is exactly 11 bytes + + while (this.running.get()) { + actionBuffer.clear(); + + // Read exactly 11 bytes for one Action + int totalBytesRead = 0; + while (totalBytesRead < 11) { + int bytesRead = clientSocket.read(actionBuffer); + if (bytesRead == -1) { + // Client disconnected + LOGGER.info("Client disconnected"); + return; + } + totalBytesRead += bytesRead; + } - String inputLine; - while ((inputLine = in.readLine()) != null && this.running.get()) { - final String command = inputLine; - // Process each command asynchronously to avoid blocking - CompletableFuture.runAsync(() -> processCommand(command, out), receiverExecutor) + // Process each action asynchronously to avoid blocking + CompletableFuture.runAsync(() -> processCommand(actionBuffer), receiverExecutor) .exceptionally( throwable -> { - LOGGER.error("Error processing command: " + command, throwable); + LOGGER.error("Error processing action: " + actionBuffer, throwable); return null; }); } @@ -207,7 +218,6 @@ public void setLatest(byte[] frameBuffer, int reward) { } private void sendObservationImmediate(Observation observation, SocketChannel clientSocket) { - LOGGER.info("Sending observation to client"); try { int totalSize = 8 + observation.frameBuffer.length; ByteBuffer buffer = ByteBuffer.allocate(totalSize); @@ -224,13 +234,17 @@ private void sendObservationImmediate(Observation observation, SocketChannel cli } } - private void processCommand(String command, PrintWriter out) { - LOGGER.info("Received command: " + command); + private void processCommand(ByteBuffer actionBuffer) { + final Action action = Action.fromBytes(actionBuffer.array()); + DataBridge.getInstance().setLatestAction(action); } private void cleanup() { LOGGER.info("Shutting down NetworkHandler..."); this.running.set(false); + + // Release all pressed keys on cleanup + ClientEventHandler.releaseAllKeys(); // Wait for threads to finish if (this.sendThread != null) { try { diff --git a/forge/src/main/java/com/mvi/mvimod/Observation.java b/forge/src/main/java/com/mvi/mvimod/Observation.java index 407dd86..46a7145 100644 --- a/forge/src/main/java/com/mvi/mvimod/Observation.java +++ b/forge/src/main/java/com/mvi/mvimod/Observation.java @@ -1,13 +1,14 @@ package com.mvi.mvimod; -public record Observation(int reward, ActionState actionState, byte[] frame) { +public record Observation( + int reward, + ActionState actionState, + byte[] frame +) { public byte[] serialize() { - // TODO: Implement this - throw new UnsupportedOperationException("Not implemented"); - } - - public static Observation deserialize(byte[] data) { - // TODO: Implement this - throw new UnsupportedOperationException("Not implemented"); + // For now, we'll keep the existing protocol that sends reward + frame + // The action state will be serialized separately later + // TODO: Implement full serialization including action state + return frame; } } From 8cdd17688562f714fdd9710f87f9ec060512b0c5 Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 2 Aug 2025 09:33:14 -0400 Subject: [PATCH 04/21] Rename send -> observation; receive -> action to better represent the data they transfer --- .../java/com/mvi/mvimod/NetworkHandler.java | 114 +++++++++--------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java index 9b07de7..5975a38 100644 --- a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java @@ -23,21 +23,23 @@ public class NetworkHandler implements Runnable { private static final Logger LOGGER = LogUtils.getLogger(); - private static final String SEND_SOCKET_PATH = "/tmp/mvi_send.sock"; - private static final String RECEIVE_SOCKET_PATH = "/tmp/mvi_receive.sock"; + private static final String OBSERVATION_SOCKET_PATH = "/tmp/mvi_observation.sock"; + private static final String ACTION_SOCKET_PATH = "/tmp/mvi_action.sock"; private static final ExecutorService senderExecutor = Executors.newCachedThreadPool(); private static final ExecutorService receiverExecutor = Executors.newCachedThreadPool(); - private Thread sendThread; - private Thread receiveThread; - private ServerSocketChannel sendSocketChannel; - private ServerSocketChannel receiveSocketChannel; + private Thread observationThread; + private Thread actionThread; + private ServerSocketChannel observationSocketChannel; + private ServerSocketChannel actionSocketChannel; private final AtomicBoolean running = new AtomicBoolean(true); // Async observation sending + // TODO: Move this AtomicReference to DataBridge private final AtomicReference latestObservation = new AtomicReference(); private final Semaphore frameAvailable = new Semaphore(0); // Observation data container + // TODO: Move to custom class that has serialization methods private static class Observation { final byte[] frameBuffer; final int reward; @@ -53,37 +55,37 @@ private static class Observation { @Override public void run() { try { - Files.deleteIfExists(Path.of(SEND_SOCKET_PATH)); - Files.deleteIfExists(Path.of(RECEIVE_SOCKET_PATH)); + Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); + Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); - sendSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - sendSocketChannel.bind(UnixDomainSocketAddress.of(SEND_SOCKET_PATH)); - sendSocketChannel.configureBlocking(true); + observationSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + observationSocketChannel.bind(UnixDomainSocketAddress.of(OBSERVATION_SOCKET_PATH)); + observationSocketChannel.configureBlocking(true); - receiveSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - receiveSocketChannel.bind(UnixDomainSocketAddress.of(RECEIVE_SOCKET_PATH)); - receiveSocketChannel.configureBlocking(true); + actionSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + actionSocketChannel.bind(UnixDomainSocketAddress.of(ACTION_SOCKET_PATH)); + actionSocketChannel.configureBlocking(true); - LOGGER.info("Socket files created: {} and {}", SEND_SOCKET_PATH, RECEIVE_SOCKET_PATH); + LOGGER.info("Socket files created: {} and {}", OBSERVATION_SOCKET_PATH, ACTION_SOCKET_PATH); // Verify socket files were actually created - if (!Files.exists(Path.of(SEND_SOCKET_PATH))) { - throw new IOException("Failed to create send socket file: " + SEND_SOCKET_PATH); + if (!Files.exists(Path.of(OBSERVATION_SOCKET_PATH))) { + throw new IOException("Failed to create observation socket file: " + OBSERVATION_SOCKET_PATH); } - if (!Files.exists(Path.of(RECEIVE_SOCKET_PATH))) { - throw new IOException("Failed to create receive socket file: " + RECEIVE_SOCKET_PATH); + if (!Files.exists(Path.of(ACTION_SOCKET_PATH))) { + throw new IOException("Failed to create action socket file: " + ACTION_SOCKET_PATH); } LOGGER.info( - "Socket files verified - Send: {}, Receive: {}", - Files.exists(Path.of(SEND_SOCKET_PATH)), - Files.exists(Path.of(RECEIVE_SOCKET_PATH))); + "Socket files verified - Observation: {}, Action: {}", + Files.exists(Path.of(OBSERVATION_SOCKET_PATH)), + Files.exists(Path.of(ACTION_SOCKET_PATH))); - sendThread = new Thread(this::acceptSendClients, "SendClients"); - receiveThread = new Thread(this::acceptReceiveClients, "ReceiveClients"); + observationThread = new Thread(this::acceptObservationClients, "ObservationClients"); + actionThread = new Thread(this::acceptActionClients, "ActionClients"); - sendThread.start(); - receiveThread.start(); + observationThread.start(); + actionThread.start(); // Keep main thread alive while server is running while (this.running.get() && !Thread.currentThread().isInterrupted()) { @@ -102,47 +104,47 @@ public void run() { } } - private void acceptSendClients() { - LOGGER.info("Send clients acceptor thread started"); + private void acceptObservationClients() { + LOGGER.info("Observation clients acceptor thread started"); while (this.running.get() && !Thread.currentThread().isInterrupted()) { try { - SocketChannel clientSocket = sendSocketChannel.accept(); - LOGGER.info("Send client connected: " + clientSocket.getRemoteAddress()); + SocketChannel clientSocket = observationSocketChannel.accept(); + LOGGER.info("Observation client connected: " + clientSocket.getRemoteAddress()); // 1MB send buffer which can fit small frames clientSocket.setOption(StandardSocketOptions.SO_SNDBUF, 1024 * 1024); - handleSendClient(clientSocket); + handleObservationClient(clientSocket); } catch (IOException e) { if (this.running.get()) { - LOGGER.error("Error accepting send client", e); + LOGGER.error("Error accepting observation client", e); } else { - LOGGER.info("Send socket channel closed, stopping accept loop"); + LOGGER.info("Observation socket channel closed, stopping accept loop"); break; } } } - LOGGER.info("Send clients acceptor thread stopped"); + LOGGER.info("Observation clients acceptor thread stopped"); } - private void acceptReceiveClients() { + private void acceptActionClients() { LOGGER.info("Receive clients acceptor thread started"); while (this.running.get() && !Thread.currentThread().isInterrupted()) { try { - SocketChannel clientSocket = receiveSocketChannel.accept(); - LOGGER.info("Receive client connected: " + clientSocket.getRemoteAddress()); - handleReceiveClient(clientSocket); + SocketChannel clientSocket = actionSocketChannel.accept(); + LOGGER.info("Action client connected: " + clientSocket.getRemoteAddress()); + handleActionClient(clientSocket); } catch (IOException e) { if (this.running.get()) { - LOGGER.error("Error accepting receive client", e); + LOGGER.error("Error accepting action client", e); } else { - LOGGER.info("Receive socket channel closed, stopping accept loop"); + LOGGER.info("Action socket channel closed, stopping accept loop"); break; } } } - LOGGER.info("Receive clients acceptor thread stopped"); + LOGGER.info("Action clients acceptor thread stopped"); } - private void handleReceiveClient(SocketChannel clientSocket) { + private void handleActionClient(SocketChannel clientSocket) { receiverExecutor.submit( () -> { try { @@ -184,7 +186,7 @@ private void handleReceiveClient(SocketChannel clientSocket) { }); } - private void handleSendClient(SocketChannel clientSocket) { + private void handleObservationClient(SocketChannel clientSocket) { senderExecutor.submit( () -> { try { @@ -234,7 +236,7 @@ private void sendObservationImmediate(Observation observation, SocketChannel cli } } - private void processCommand(ByteBuffer actionBuffer) { + private void processAction(ByteBuffer actionBuffer) { final Action action = Action.fromBytes(actionBuffer.array()); DataBridge.getInstance().setLatestAction(action); } @@ -246,19 +248,19 @@ private void cleanup() { // Release all pressed keys on cleanup ClientEventHandler.releaseAllKeys(); // Wait for threads to finish - if (this.sendThread != null) { + if (this.observationThread != null) { try { - this.sendThread.interrupt(); - this.sendThread.join(5000); // Wait up to 5 seconds + this.observationThread.interrupt(); + this.observationThread.join(5000); // Wait up to 5 seconds } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } - if (this.receiveThread != null) { + if (this.actionThread != null) { try { - this.receiveThread.interrupt(); - this.receiveThread.join(5000); // Wait up to 5 seconds + this.actionThread.interrupt(); + this.actionThread.join(5000); // Wait up to 5 seconds } catch (InterruptedException e) { Thread.currentThread().interrupt(); } @@ -269,19 +271,19 @@ private void cleanup() { receiverExecutor.shutdown(); try { - if (this.sendSocketChannel != null) { - this.sendSocketChannel.close(); + if (this.observationSocketChannel != null) { + this.observationSocketChannel.close(); } - if (this.receiveSocketChannel != null) { - this.receiveSocketChannel.close(); + if (this.actionSocketChannel != null) { + this.actionSocketChannel.close(); } } catch (IOException e) { LOGGER.error("Cleanup error: " + e.getMessage()); } try { - Files.deleteIfExists(Path.of(SEND_SOCKET_PATH)); - Files.deleteIfExists(Path.of(RECEIVE_SOCKET_PATH)); + Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); + Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); } catch (IOException e) { LOGGER.error("Cleanup error: " + e.getMessage()); } From 660e9b6f2900875a30ce0285790f89369eff9b73 Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 2 Aug 2025 11:18:55 -0400 Subject: [PATCH 05/21] Refactor Action and ActionState --- .../src/main/java/com/mvi/mvimod/Action.java | 191 +++++---------- .../java/com/mvi/mvimod/ActionHandler.java | 57 +++++ .../main/java/com/mvi/mvimod/ActionState.java | 202 ++++++++-------- .../com/mvi/mvimod/ClientEventHandler.java | 227 +++++------------- .../java/com/mvi/mvimod/NetworkHandler.java | 27 +-- .../main/java/com/mvi/mvimod/Observation.java | 5 +- 6 files changed, 305 insertions(+), 404 deletions(-) create mode 100644 forge/src/main/java/com/mvi/mvimod/ActionHandler.java diff --git a/forge/src/main/java/com/mvi/mvimod/Action.java b/forge/src/main/java/com/mvi/mvimod/Action.java index 7433810..d79ac19 100644 --- a/forge/src/main/java/com/mvi/mvimod/Action.java +++ b/forge/src/main/java/com/mvi/mvimod/Action.java @@ -3,117 +3,58 @@ import java.nio.ByteBuffer; /* - * This class represents the schema for communication between the client and server. - * It is a data structure that can pack and unpack the action state - * into a byte array and back. + * This class represents the complete action schema for communication between the client and server. + * It contains both persistent actions (button states that continue until released) and + * non-persistent actions (like mouse movement and menu commands). * * The format is as follows: - * - UP (1 bit) - * - DOWN (1 bit) - * - LEFT (1 bit) - * - RIGHT (1 bit) - * - JUMP (1 bit) - * - SNEAK (1 bit) - * - SPRINT (1 bit) - * - INVENTORY (1 bit) - * - DROP (1 bit) - * - SWAP (1 bit) - * - USE (1 bit) - * - ATTACK (1 bit) - * - PICK_ITEM (1 bit) - * - HOTBAR_1 (1 bit) - * - HOTBAR_2 (1 bit) - * - HOTBAR_3 (1 bit) - * - HOTBAR_4 (1 bit) - * - HOTBAR_5 (1 bit) - * - HOTBAR_6 (1 bit) - * - HOTBAR_7 (1 bit) - * - HOTBAR_8 (1 bit) + * - Persistent actions (3 bytes) - see ActionState for bit layout * - EXIT_MENU (1 bit) - * - PADDING (1 bit) - * - PADDING (1 bit) + * - PADDING (7 bits) * - Mouse control X (4 bytes) * - Mouse control Y (4 bytes) */ public record Action( - boolean up, - boolean down, - boolean left, - boolean right, - boolean jump, - boolean sneak, - boolean sprint, - boolean inventory, - boolean drop, - boolean swap, - boolean use, - boolean attack, - boolean pick_item, - boolean hotbar1, - boolean hotbar2, - boolean hotbar3, - boolean hotbar4, - boolean hotbar5, - boolean hotbar6, - boolean hotbar7, - boolean hotbar8, + ActionState actionState, boolean exitMenu, float mouseControlX, float mouseControlY ) { + public boolean up() { return actionState.up(); } + public boolean down() { return actionState.down(); } + public boolean left() { return actionState.left(); } + public boolean right() { return actionState.right(); } + public boolean jump() { return actionState.jump(); } + public boolean sneak() { return actionState.sneak(); } + public boolean sprint() { return actionState.sprint(); } + public boolean inventory() { return actionState.inventory(); } + public boolean drop() { return actionState.drop(); } + public boolean swap() { return actionState.swap(); } + public boolean use() { return actionState.use(); } + public boolean attack() { return actionState.attack(); } + public boolean pick_item() { return actionState.pick_item(); } + public boolean hotbar1() { return actionState.hotbar1(); } + public boolean hotbar2() { return actionState.hotbar2(); } + public boolean hotbar3() { return actionState.hotbar3(); } + public boolean hotbar4() { return actionState.hotbar4(); } + public boolean hotbar5() { return actionState.hotbar5(); } + public boolean hotbar6() { return actionState.hotbar6(); } + public boolean hotbar7() { return actionState.hotbar7(); } + public boolean hotbar8() { return actionState.hotbar8(); } + public byte[] toBytes() { - ByteBuffer buffer = ByteBuffer.allocate(3 + 4 + 4); + ByteBuffer buffer = ByteBuffer.allocate(3 + 1 + 4 + 4); - int firstByte = 0; - firstByte |= (up ? 1 : 0); - firstByte <<= 1; - firstByte |= (down ? 1 : 0); - firstByte <<= 1; - firstByte |= (left ? 1 : 0); - firstByte <<= 1; - firstByte |= (right ? 1 : 0); - firstByte <<= 1; - firstByte |= (jump ? 1 : 0); - firstByte <<= 1; - firstByte |= (sneak ? 1 : 0); - firstByte <<= 1; - firstByte |= (sprint ? 1 : 0); - firstByte <<= 1; - firstByte |= (inventory ? 1 : 0); - buffer.put((byte) firstByte); + // Add persistent action bytes + byte[] persistentBytes = actionState.toBytes(); + buffer.put(persistentBytes); - int secondByte = 0; - secondByte |= (drop ? 1 : 0); - secondByte <<= 1; - secondByte |= (swap ? 1 : 0); - secondByte <<= 1; - secondByte |= (use ? 1 : 0); - secondByte <<= 1; - secondByte |= (attack ? 1 : 0); - secondByte <<= 1; - secondByte |= (pick_item ? 1 : 0); - secondByte <<= 1; - secondByte |= (hotbar1 ? 1 : 0); - secondByte <<= 1; - secondByte |= (hotbar2 ? 1 : 0); - secondByte <<= 1; - secondByte |= (hotbar3 ? 1 : 0); - buffer.put((byte) secondByte); - - int thirdByte = 0; - thirdByte |= (hotbar4 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar5 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar6 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar7 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar8 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (exitMenu ? 1 : 0); - buffer.put((byte) thirdByte); + // Add exit menu flag with padding + int menuByte = 0; + menuByte |= (exitMenu ? 1 : 0); + buffer.put((byte) menuByte); + // Add mouse controls buffer.putFloat(mouseControlX); buffer.putFloat(mouseControlY); @@ -123,38 +64,38 @@ public byte[] toBytes() { public static Action fromBytes(byte[] bytes) { ByteBuffer buffer = ByteBuffer.wrap(bytes); - int firstByte = buffer.get(); - int secondByte = buffer.get(); - int thirdByte = buffer.get(); + // Extract persistent action bytes + byte[] persistentBytes = new byte[3]; + buffer.get(persistentBytes); + ActionState actionState = ActionState.fromBytes(persistentBytes); + + // Extract exit menu flag + int menuByte = buffer.get() & 0xFF; + boolean exitMenu = ((menuByte >> 7) & 1) == 1; + // Extract mouse controls float mouseControlX = buffer.getFloat(); float mouseControlY = buffer.getFloat(); - return new Action( - ((firstByte >> 0) & 1) == 1, - ((firstByte >> 1) & 1) == 1, - ((firstByte >> 2) & 1) == 1, - ((firstByte >> 3) & 1) == 1, - ((firstByte >> 4) & 1) == 1, - ((firstByte >> 5) & 1) == 1, - ((firstByte >> 6) & 1) == 1, - ((firstByte >> 7) & 1) == 1, - ((secondByte >> 0) & 1) == 1, - ((secondByte >> 1) & 1) == 1, - ((secondByte >> 2) & 1) == 1, - ((secondByte >> 3) & 1) == 1, - ((secondByte >> 4) & 1) == 1, - ((secondByte >> 5) & 1) == 1, - ((secondByte >> 6) & 1) == 1, - ((secondByte >> 7) & 1) == 1, - ((thirdByte >> 0) & 1) == 1, - ((thirdByte >> 1) & 1) == 1, - ((thirdByte >> 2) & 1) == 1, - ((thirdByte >> 3) & 1) == 1, - ((thirdByte >> 4) & 1) == 1, - ((thirdByte >> 5) & 1) == 1, - mouseControlX, - mouseControlY + return new Action(actionState, exitMenu, mouseControlX, mouseControlY); + } + + // Convenience constructor for creating actions with individual persistent fields + public Action( + boolean up, boolean down, boolean left, boolean right, + boolean jump, boolean sneak, boolean sprint, boolean inventory, + boolean drop, boolean swap, boolean use, boolean attack, boolean pick_item, + boolean hotbar1, boolean hotbar2, boolean hotbar3, boolean hotbar4, + boolean hotbar5, boolean hotbar6, boolean hotbar7, boolean hotbar8, + boolean exitMenu, float mouseControlX, float mouseControlY + ) { + this( + new ActionState( + up, down, left, right, jump, sneak, sprint, inventory, + drop, swap, use, attack, pick_item, + hotbar1, hotbar2, hotbar3, hotbar4, hotbar5, hotbar6, hotbar7, hotbar8 + ), + exitMenu, mouseControlX, mouseControlY ); } -} +} \ No newline at end of file diff --git a/forge/src/main/java/com/mvi/mvimod/ActionHandler.java b/forge/src/main/java/com/mvi/mvimod/ActionHandler.java new file mode 100644 index 0000000..071af80 --- /dev/null +++ b/forge/src/main/java/com/mvi/mvimod/ActionHandler.java @@ -0,0 +1,57 @@ +package com.mvi.mvimod; + +import com.mojang.logging.LogUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.KeyMapping; +import org.slf4j.Logger; + +public class ActionHandler { + private static final Logger LOGGER = LogUtils.getLogger(); + + public static ActionState getActionState(Minecraft mc) { + final ActionState actionState = new ActionState( + mc.options.keyUp.isDown(), + mc.options.keyDown.isDown(), + mc.options.keyLeft.isDown(), + mc.options.keyRight.isDown(), + mc.options.keyJump.isDown(), + mc.options.keyShift.isDown(), + mc.options.keySprint.isDown(), + mc.options.keyInventory.isDown(), + mc.options.keyDrop.isDown(), + mc.options.keySwapOffhand.isDown(), + mc.options.keyUse.isDown(), + mc.options.keyAttack.isDown(), + mc.options.keyPickItem.isDown(), + mc.options.keyHotbarSlots[0].isDown(), + mc.options.keyHotbarSlots[1].isDown(), + mc.options.keyHotbarSlots[2].isDown(), + mc.options.keyHotbarSlots[3].isDown(), + mc.options.keyHotbarSlots[4].isDown(), + mc.options.keyHotbarSlots[5].isDown(), + mc.options.keyHotbarSlots[6].isDown(), + mc.options.keyHotbarSlots[7].isDown() + ); + return actionState; + } + + public static void pressKeyMapping(KeyMapping keyMapping, boolean down) { + if (down && !keyMapping.isDown()) { + keyMapping.setDown(true); + } else if (!down && keyMapping.isDown()) { + keyMapping.setDown(false); + } + } + + public static void exitMenu(Minecraft mc, boolean exit) { + if (exit) { + mc.setScreen(null); + } + } + + public static void turnPlayer(Minecraft mc, float deltaX, float deltaY) { + if (deltaX != 0.0f && deltaY != 0.0f) { + mc.player.turn(deltaX, deltaY); + } + } +} diff --git a/forge/src/main/java/com/mvi/mvimod/ActionState.java b/forge/src/main/java/com/mvi/mvimod/ActionState.java index 2756c55..4501096 100644 --- a/forge/src/main/java/com/mvi/mvimod/ActionState.java +++ b/forge/src/main/java/com/mvi/mvimod/ActionState.java @@ -1,100 +1,114 @@ package com.mvi.mvimod; -import java.util.HashMap; -import java.util.Map; +import java.nio.ByteBuffer; -public class ActionState { - private final Map keyStates; - private float mouseDeltaX = 0.0f; - private float mouseDeltaY = 0.0f; - - public ActionState() { - this.keyStates = new HashMap<>(); - - // Initialize all tracked keys to false - initializeKeyStates(); - } - - private void initializeKeyStates() { - // Movement keys - keyStates.put("UP", false); - keyStates.put("DOWN", false); - keyStates.put("LEFT", false); - keyStates.put("RIGHT", false); - - // Action keys - keyStates.put("JUMP", false); // Jump - keyStates.put("SNEAK", false); // Sneak - keyStates.put("SPRINT", false); // Sprint - keyStates.put("INVENTORY", false); // Inventory - keyStates.put("DROP", false); // Drop - keyStates.put("CANCEL", false); // Menu/Cancel - keyStates.put("SWAP", false); // Swap off-hand - keyStates.put("USE", false); - keyStates.put("ATTACK", false); - keyStates.put("PICK_ITEM", false); - - // Menu control - keyStates.put("MENU_EXIT", false); // ESC key for exiting menus - - // Number keys for hotbar - for (int i = 1; i <= 9; i++) { - keyStates.put("HOTBAR_" +String.valueOf(i), false); +public record ActionState( + boolean up, + boolean down, + boolean left, + boolean right, + boolean jump, + boolean sneak, + boolean sprint, + boolean inventory, + boolean drop, + boolean swap, + boolean use, + boolean attack, + boolean pick_item, + boolean hotbar1, + boolean hotbar2, + boolean hotbar3, + boolean hotbar4, + boolean hotbar5, + boolean hotbar6, + boolean hotbar7, + boolean hotbar8 +) { + public byte[] toBytes() { + ByteBuffer buffer = ByteBuffer.allocate(3); + + int firstByte = 0; + firstByte |= (up ? 1 : 0); + firstByte <<= 1; + firstByte |= (down ? 1 : 0); + firstByte <<= 1; + firstByte |= (left ? 1 : 0); + firstByte <<= 1; + firstByte |= (right ? 1 : 0); + firstByte <<= 1; + firstByte |= (jump ? 1 : 0); + firstByte <<= 1; + firstByte |= (sneak ? 1 : 0); + firstByte <<= 1; + firstByte |= (sprint ? 1 : 0); + firstByte <<= 1; + firstByte |= (inventory ? 1 : 0); + buffer.put((byte) firstByte); + + int secondByte = 0; + secondByte |= (drop ? 1 : 0); + secondByte <<= 1; + secondByte |= (swap ? 1 : 0); + secondByte <<= 1; + secondByte |= (use ? 1 : 0); + secondByte <<= 1; + secondByte |= (attack ? 1 : 0); + secondByte <<= 1; + secondByte |= (pick_item ? 1 : 0); + secondByte <<= 1; + secondByte |= (hotbar1 ? 1 : 0); + secondByte <<= 1; + secondByte |= (hotbar2 ? 1 : 0); + secondByte <<= 1; + secondByte |= (hotbar3 ? 1 : 0); + buffer.put((byte) secondByte); + + int thirdByte = 0; + thirdByte |= (hotbar4 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar5 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar6 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar7 ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (hotbar8 ? 1 : 0); + // Leave 3 bits unused for padding + buffer.put((byte) thirdByte); + + return buffer.array(); } - } - - public boolean isKeyPressed(String key) { - return keyStates.getOrDefault(key, false); - } - - public void setKeyPressed(String key, boolean pressed) { - keyStates.put(key, pressed); - } - - public Map getKeyStates() { - return new HashMap<>(keyStates); - } - - public void releaseAllKeys() { - for (String key : keyStates.keySet()) { - keyStates.put(key, false); + + public static ActionState fromBytes(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + int firstByte = buffer.get() & 0xFF; + int secondByte = buffer.get() & 0xFF; + int thirdByte = buffer.get() & 0xFF; + + return new ActionState( + ((firstByte >> 7) & 1) == 1, // up + ((firstByte >> 6) & 1) == 1, // down + ((firstByte >> 5) & 1) == 1, // left + ((firstByte >> 4) & 1) == 1, // right + ((firstByte >> 3) & 1) == 1, // jump + ((firstByte >> 2) & 1) == 1, // sneak + ((firstByte >> 1) & 1) == 1, // sprint + ((firstByte >> 0) & 1) == 1, // inventory + ((secondByte >> 7) & 1) == 1, // drop + ((secondByte >> 6) & 1) == 1, // swap + ((secondByte >> 5) & 1) == 1, // use + ((secondByte >> 4) & 1) == 1, // attack + ((secondByte >> 3) & 1) == 1, // pick_item + ((secondByte >> 2) & 1) == 1, // hotbar1 + ((secondByte >> 1) & 1) == 1, // hotbar2 + ((secondByte >> 0) & 1) == 1, // hotbar3 + ((thirdByte >> 7) & 1) == 1, // hotbar4 + ((thirdByte >> 6) & 1) == 1, // hotbar5 + ((thirdByte >> 5) & 1) == 1, // hotbar6 + ((thirdByte >> 4) & 1) == 1, // hotbar7 + ((thirdByte >> 3) & 1) == 1 // hotbar8 + ); } - // Reset mouse deltas when releasing all keys - mouseDeltaX = 0.0f; - mouseDeltaY = 0.0f; - } - - /** - * Set mouse movement delta for head/camera control - * @param deltaX Horizontal mouse movement (positive = right) - * @param deltaY Vertical mouse movement (positive = up) - */ - public void setMouseDelta(float deltaX, float deltaY) { - this.mouseDeltaX = deltaX; - this.mouseDeltaY = deltaY; - } - - /** - * Get horizontal mouse movement delta - * @return Mouse delta X value - */ - public float getMouseDeltaX() { - return mouseDeltaX; - } - - /** - * Get vertical mouse movement delta - * @return Mouse delta Y value - */ - public float getMouseDeltaY() { - return mouseDeltaY; - } - - /** - * Reset mouse deltas (typically called after processing movement) - */ - public void resetMouseDeltas() { - mouseDeltaX = 0.0f; - mouseDeltaY = 0.0f; - } } diff --git a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java index c731f71..24b0cae 100644 --- a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java @@ -3,12 +3,7 @@ import com.mojang.blaze3d.platform.Window; import com.mojang.logging.LogUtils; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; import net.minecraft.client.Minecraft; -import net.minecraft.client.KeyMapping; -import net.minecraft.world.entity.player.Player; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; import net.minecraftforge.event.entity.living.LivingHurtEvent; @@ -22,17 +17,9 @@ public class ClientEventHandler { private static final Logger LOGGER = LogUtils.getLogger(); private static DataBridge dataBridge = DataBridge.getInstance(); - // Track action state across ticks - private static ActionState actionState = new ActionState(); - - // Map string keys to Minecraft KeyMapping objects - private static final Map keyMappings = new HashMap<>(); - private static boolean keyMappingsInitialized = false; - @SubscribeEvent public static void onServerStarting(ServerStartingEvent event) { LOGGER.info("MVI Mod Server Starting - Network handler is managed on client side"); - initializeKeyMappings(); } @SubscribeEvent @@ -40,165 +27,62 @@ public static void onServerStopping(ServerStoppingEvent event) { LOGGER.info("MVI Mod Server Stopping"); } + private static void processAction(Action action) { + Minecraft mc = Minecraft.getInstance(); + ActionHandler.pressKeyMapping(mc.options.keyUp, action.up()); + ActionHandler.pressKeyMapping(mc.options.keyDown, action.down()); + ActionHandler.pressKeyMapping(mc.options.keyLeft, action.left()); + ActionHandler.pressKeyMapping(mc.options.keyRight, action.right()); + ActionHandler.pressKeyMapping(mc.options.keyJump, action.jump()); + ActionHandler.pressKeyMapping(mc.options.keyShift, action.sneak()); + ActionHandler.pressKeyMapping(mc.options.keySprint, action.sprint()); + ActionHandler.pressKeyMapping(mc.options.keyInventory, action.inventory()); + ActionHandler.pressKeyMapping(mc.options.keyDrop, action.drop()); + ActionHandler.pressKeyMapping(mc.options.keySwapOffhand, action.swap()); + ActionHandler.pressKeyMapping(mc.options.keyUse, action.use()); + ActionHandler.pressKeyMapping(mc.options.keyAttack, action.attack()); + ActionHandler.pressKeyMapping(mc.options.keyPickItem, action.pick_item()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[0], action.hotbar1()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[1], action.hotbar2()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[2], action.hotbar3()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[3], action.hotbar4()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[4], action.hotbar5()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[5], action.hotbar6()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[6], action.hotbar7()); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[7], action.hotbar8()); + ActionHandler.exitMenu(mc, action.exitMenu()); + ActionHandler.turnPlayer(mc, action.mouseControlX(), action.mouseControlY()); + } + @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { - - ArrayList commands = dataBridge.emptyCommandQueue(); - for (String command : commands) { - processCommand(command); + final Action action = dataBridge.getLatestAction(); + if (action != null) { + processAction(action); } if (event.phase == TickEvent.Phase.END) { - // Increment action state timings - actionState.incrementTimings(); - - // TODO: Move to data bridge? - int reward = packageReward(); - ActionState currentActionState = captureActionState(); + // int reward = packageReward(); + ActionState currentActionState = ActionHandler.getActionState(Minecraft.getInstance()); byte[] frame = captureFrame(); - dataBridge.sendObservation(new Observation(reward, currentActionState, frame)); + dataBridge.sendObservation(new Observation(0.0, currentActionState, frame)); } } @SubscribeEvent public static void onPlayerHurt(LivingHurtEvent event) { - if (event.getEntity() instanceof Player) { - dataBridge.sendEvent("PLAYER_HURT", String.valueOf(event.getAmount())); - } + // if (event.getEntity() instanceof Player) { + // dataBridge.sendEvent("PLAYER_HURT", String.valueOf(event.getAmount())); + // } } @SubscribeEvent public static void onPlayerDeath(LivingDeathEvent event) { - if (event.getEntity() instanceof Player) { - dataBridge.sendEvent("PLAYER_DEATH", "-100.0"); - } - } - - private static void initializeKeyMappings() { - Minecraft mc = Minecraft.getInstance(); - if (mc.options == null) return; - - // Movement keys - keyMappings.put("UP", mc.options.keyUp); - keyMappings.put("DOWN", mc.options.keyDown); - keyMappings.put("LEFT", mc.options.keyLeft); - keyMappings.put("RIGHT", mc.options.keyRight); - - // Action keys - keyMappings.put("JUMP", mc.options.keyJump); - keyMappings.put("SNEAK", mc.options.keyShift); - keyMappings.put("SPRINT", mc.options.keySprint); - keyMappings.put("INVENTORY", mc.options.keyInventory); - keyMappings.put("DROP", mc.options.keyDrop); - keyMappings.put("SWAP", mc.options.keySwapOffhand); - keyMappings.put("USE", mc.options.keyUse); - keyMappings.put("ATTACK", mc.options.keyAttack); - keyMappings.put("PICK_ITEM", mc.options.keyPickItem); - - // Number keys for hotbar - KeyMapping[] hotbarKeys = mc.options.keyHotbarSlots; - for (int i = 0; i < hotbarKeys.length && i < 9; i++) { - keyMappings.put("HOTBAR_" + String.valueOf(i + 1), hotbarKeys[i]); - } - - keyMappingsInitialized = true; - LOGGER.info("Key mappings initialized with {} keys", keyMappings.size()); - } - - private static void processCommand(String command) { - LOGGER.debug("Processing command: {}", command); - - if (command.startsWith("PRESS_")) { - String keyName = command.substring(6); - pressKey(keyName); - } else if (command.startsWith("RELEASE_")) { - String keyName = command.substring(8); - releaseKey(keyName); - } else if (command.startsWith("MOUSE_MOVE_")) { - // Format: MOUSE_MOVE_X_Y (e.g., MOUSE_MOVE_5.0_-2.5) - String coords = command.substring(11); - String[] parts = coords.split("_"); - if (parts.length == 2) { - try { - float deltaX = Float.parseFloat(parts[0]); - float deltaY = Float.parseFloat(parts[1]); - moveMouseForHeadControl(deltaX, deltaY); - } catch (NumberFormatException e) { - LOGGER.warn("Invalid mouse movement coordinates: {}", coords); - } - } - } else if (command.equals("ESCAPE")) { - pressEscapeKey(); - } else { - LOGGER.warn("Unknown command format: {}", command); - } + // if (event.getEntity() instanceof Player) { + // dataBridge.sendEvent("PLAYER_DEATH", "-100.0"); + // } } - private static void pressKey(String keyName) { - KeyMapping keyMapping = keyMappings.get(keyName); - if (keyMapping != null) { - if (!keyMapping.isDown()) { - keyMapping.setDown(true); - actionState.setKeyPressed(keyName, true); - LOGGER.debug("Pressed key: {}", keyName); - } - } else { - LOGGER.warn("Unknown key: {}", keyName); - } - } - - private static void releaseKey(String keyName) { - KeyMapping keyMapping = keyMappings.get(keyName); - if (keyMapping != null) { - if (keyMapping.isDown()) { - keyMapping.setDown(false); - actionState.setKeyPressed(keyName, false); - LOGGER.debug("Released key: {}", keyName); - } - } else { - LOGGER.warn("Unknown key: {}", keyName); - } - } - - private static void moveMouseForHeadControl(float deltaX, float deltaY) { - Minecraft mc = Minecraft.getInstance(); - if (mc.player != null) { - // Store mouse deltas in action state - actionState.setMouseDelta(deltaX, deltaY); - - // Apply mouse movement to player rotation - // deltaX affects yaw (horizontal looking) - // deltaY affects pitch (vertical looking) - mc.player.turn(deltaX * 0.15, deltaY * 0.15); - - LOGGER.debug("Applied mouse movement: deltaX={}, deltaY={}", deltaX, deltaY); - } - } - - private static void pressEscapeKey() { - Minecraft mc = Minecraft.getInstance(); - if (mc.screen != null) { - // Close current screen/menu - mc.setScreen(null); - } - actionState.setKeyPressed("MENU_EXIT", true); - LOGGER.debug("Pressed ESC key - closing menu"); - } - - private static int packageReward() { - // TODO: Get rewards from the reward queue - return 0; // Placeholder - } - - private static ActionState captureActionState() { - // Return a copy of the current action state - ActionState currentState = new ActionState(); - for (Map.Entry entry : actionState.getKeyStates().entrySet()) { - currentState.setKeyPressed(entry.getKey(), entry.getValue()); - } - return currentState; - } - private static byte[] captureFrame() { Minecraft mc = Minecraft.getInstance(); if (mc.level != null && mc.player != null) { @@ -223,16 +107,27 @@ private static byte[] captureScreenshot(Window window) { * Release all currently pressed keys (for cleanup on disconnect) */ public static void releaseAllKeys() { - if (!keyMappingsInitialized) return; - - LOGGER.info("Releasing all pressed keys for cleanup"); - for (Map.Entry entry : keyMappings.entrySet()) { - KeyMapping keyMapping = entry.getValue(); - if (keyMapping.isDown()) { - keyMapping.setDown(false); - LOGGER.debug("Released key during cleanup: {}", entry.getKey()); - } - } - actionState.releaseAllKeys(); + Minecraft mc = Minecraft.getInstance(); + ActionHandler.pressKeyMapping(mc.options.keyUp, false); + ActionHandler.pressKeyMapping(mc.options.keyDown, false); + ActionHandler.pressKeyMapping(mc.options.keyLeft, false); + ActionHandler.pressKeyMapping(mc.options.keyRight, false); + ActionHandler.pressKeyMapping(mc.options.keyJump, false); + ActionHandler.pressKeyMapping(mc.options.keyShift, false); + ActionHandler.pressKeyMapping(mc.options.keySprint, false); + ActionHandler.pressKeyMapping(mc.options.keyInventory, false); + ActionHandler.pressKeyMapping(mc.options.keyDrop, false); + ActionHandler.pressKeyMapping(mc.options.keySwapOffhand, false); + ActionHandler.pressKeyMapping(mc.options.keyUse, false); + ActionHandler.pressKeyMapping(mc.options.keyAttack, false); + ActionHandler.pressKeyMapping(mc.options.keyPickItem, false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[0], false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[1], false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[2], false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[3], false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[4], false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[5], false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[6], false); + ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[7], false); } } diff --git a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java index 5975a38..9c2761a 100644 --- a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java @@ -1,10 +1,7 @@ package com.mvi.mvimod; import com.mojang.logging.LogUtils; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; import java.net.StandardProtocolFamily; import java.net.StandardSocketOptions; import java.net.UnixDomainSocketAddress; @@ -25,8 +22,8 @@ public class NetworkHandler implements Runnable { private static final Logger LOGGER = LogUtils.getLogger(); private static final String OBSERVATION_SOCKET_PATH = "/tmp/mvi_observation.sock"; private static final String ACTION_SOCKET_PATH = "/tmp/mvi_action.sock"; - private static final ExecutorService senderExecutor = Executors.newCachedThreadPool(); - private static final ExecutorService receiverExecutor = Executors.newCachedThreadPool(); + private static final ExecutorService observationExecutor = Executors.newCachedThreadPool(); + private static final ExecutorService actionExecutor = Executors.newCachedThreadPool(); private Thread observationThread; private Thread actionThread; private ServerSocketChannel observationSocketChannel; @@ -42,10 +39,10 @@ public class NetworkHandler implements Runnable { // TODO: Move to custom class that has serialization methods private static class Observation { final byte[] frameBuffer; - final int reward; + final double reward; final long timestamp; - Observation(byte[] frameBuffer, int reward) { + Observation(byte[] frameBuffer, double reward) { this.frameBuffer = frameBuffer; this.reward = reward; this.timestamp = System.currentTimeMillis(); @@ -126,7 +123,7 @@ private void acceptObservationClients() { } private void acceptActionClients() { - LOGGER.info("Receive clients acceptor thread started"); + LOGGER.info("Action clients acceptor thread started"); while (this.running.get() && !Thread.currentThread().isInterrupted()) { try { SocketChannel clientSocket = actionSocketChannel.accept(); @@ -145,7 +142,7 @@ private void acceptActionClients() { } private void handleActionClient(SocketChannel clientSocket) { - receiverExecutor.submit( + actionExecutor.submit( () -> { try { clientSocket.configureBlocking(true); @@ -167,7 +164,7 @@ private void handleActionClient(SocketChannel clientSocket) { } // Process each action asynchronously to avoid blocking - CompletableFuture.runAsync(() -> processCommand(actionBuffer), receiverExecutor) + CompletableFuture.runAsync(() -> processAction(actionBuffer), actionExecutor) .exceptionally( throwable -> { LOGGER.error("Error processing action: " + actionBuffer, throwable); @@ -187,7 +184,7 @@ private void handleActionClient(SocketChannel clientSocket) { } private void handleObservationClient(SocketChannel clientSocket) { - senderExecutor.submit( + observationExecutor.submit( () -> { try { while (this.running.get()) { @@ -212,7 +209,7 @@ private void handleObservationClient(SocketChannel clientSocket) { }); } - public void setLatest(byte[] frameBuffer, int reward) { + public void setLatest(byte[] frameBuffer, double reward) { Observation observation = new Observation(frameBuffer, reward); latestObservation.set(observation); frameAvailable.drainPermits(); @@ -223,7 +220,7 @@ private void sendObservationImmediate(Observation observation, SocketChannel cli try { int totalSize = 8 + observation.frameBuffer.length; ByteBuffer buffer = ByteBuffer.allocate(totalSize); - buffer.putInt(observation.reward); + buffer.putDouble(observation.reward); buffer.putInt(observation.frameBuffer.length); buffer.put(observation.frameBuffer); buffer.flip(); @@ -267,8 +264,8 @@ private void cleanup() { } // Shutdown executors - senderExecutor.shutdown(); - receiverExecutor.shutdown(); + observationExecutor.shutdown(); + actionExecutor.shutdown(); try { if (this.observationSocketChannel != null) { diff --git a/forge/src/main/java/com/mvi/mvimod/Observation.java b/forge/src/main/java/com/mvi/mvimod/Observation.java index 46a7145..9c91814 100644 --- a/forge/src/main/java/com/mvi/mvimod/Observation.java +++ b/forge/src/main/java/com/mvi/mvimod/Observation.java @@ -1,14 +1,11 @@ package com.mvi.mvimod; public record Observation( - int reward, + double reward, ActionState actionState, byte[] frame ) { public byte[] serialize() { - // For now, we'll keep the existing protocol that sends reward + frame - // The action state will be serialized separately later - // TODO: Implement full serialization including action state return frame; } } From fdce5a85029ebc570620dfaecc0a07f2d1590f8c Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 2 Aug 2025 11:58:42 -0400 Subject: [PATCH 06/21] Movement commands working with test client; Inventory and some mouse control not working --- .../src/main/java/com/mvi/mvimod/Action.java | 7 +- .../com/mvi/mvimod/ClientEventHandler.java | 26 +- .../main/java/com/mvi/mvimod/DataBridge.java | 8 +- .../java/com/mvi/mvimod/NetworkHandler.java | 5 +- test_action_client.py | 645 ++++++++++++++++++ 5 files changed, 673 insertions(+), 18 deletions(-) create mode 100644 test_action_client.py diff --git a/forge/src/main/java/com/mvi/mvimod/Action.java b/forge/src/main/java/com/mvi/mvimod/Action.java index d79ac19..3536114 100644 --- a/forge/src/main/java/com/mvi/mvimod/Action.java +++ b/forge/src/main/java/com/mvi/mvimod/Action.java @@ -1,6 +1,8 @@ package com.mvi.mvimod; import java.nio.ByteBuffer; +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; /* * This class represents the complete action schema for communication between the client and server. @@ -20,6 +22,8 @@ public record Action( float mouseControlX, float mouseControlY ) { + private static final Logger LOGGER = LogUtils.getLogger(); + public boolean up() { return actionState.up(); } public boolean down() { return actionState.down(); } public boolean left() { return actionState.left(); } @@ -51,7 +55,7 @@ public byte[] toBytes() { // Add exit menu flag with padding int menuByte = 0; - menuByte |= (exitMenu ? 1 : 0); + menuByte |= (exitMenu ? 1 << 7 : 0); buffer.put((byte) menuByte); // Add mouse controls @@ -62,6 +66,7 @@ public byte[] toBytes() { } public static Action fromBytes(byte[] bytes) { + LOGGER.info("Action fromBytes: {}", bytes); ByteBuffer buffer = ByteBuffer.wrap(bytes); // Extract persistent action bytes diff --git a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java index 24b0cae..93671b1 100644 --- a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java @@ -56,16 +56,19 @@ private static void processAction(Action action) { @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { - final Action action = dataBridge.getLatestAction(); - if (action != null) { - processAction(action); - } + Minecraft mc = Minecraft.getInstance(); + if (mc.level != null && mc.player != null) { + final Action action = dataBridge.getLatestAction(); + if (action != null) { + processAction(action); + } - if (event.phase == TickEvent.Phase.END) { - // int reward = packageReward(); - ActionState currentActionState = ActionHandler.getActionState(Minecraft.getInstance()); - byte[] frame = captureFrame(); - dataBridge.sendObservation(new Observation(0.0, currentActionState, frame)); + if (event.phase == TickEvent.Phase.END) { + // int reward = packageReward(); + final ActionState currentActionState = ActionHandler.getActionState(Minecraft.getInstance()); + final byte[] frame = captureFrame(); + dataBridge.sendObservation(new Observation(0.0, currentActionState, frame)); + } } } @@ -85,10 +88,7 @@ public static void onPlayerDeath(LivingDeathEvent event) { private static byte[] captureFrame() { Minecraft mc = Minecraft.getInstance(); - if (mc.level != null && mc.player != null) { - return captureScreenshot(mc.getWindow()); - } - return null; + return captureScreenshot(mc.getWindow()); } private static byte[] captureScreenshot(Window window) { diff --git a/forge/src/main/java/com/mvi/mvimod/DataBridge.java b/forge/src/main/java/com/mvi/mvimod/DataBridge.java index 941f1d7..8141938 100644 --- a/forge/src/main/java/com/mvi/mvimod/DataBridge.java +++ b/forge/src/main/java/com/mvi/mvimod/DataBridge.java @@ -28,7 +28,7 @@ public void setNetworkHandler(NetworkHandler handler) { public void sendObservation(Observation obs) { if (networkHandler != null) { - LOGGER.info("DataBridge sending frame data (size: {} bytes)", obs.frame().length); + // LOGGER.info("DataBridge sending frame data (size: {} bytes)", obs.frame().length); networkHandler.setLatest(obs.frame(), obs.reward()); } else { LOGGER.warn("Cannot send frame - NetworkHandler is null"); @@ -40,6 +40,10 @@ public void setLatestAction(Action action) { } public Action getLatestAction() { - return latestAction.get(); + Action action = latestAction.getAndSet(null); + if (action != null) { + LOGGER.info("DataBridge getting latest action: {}", action); + } + return action; } } diff --git a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java index 9c2761a..9a4df71 100644 --- a/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/NetworkHandler.java @@ -146,14 +146,14 @@ private void handleActionClient(SocketChannel clientSocket) { () -> { try { clientSocket.configureBlocking(true); - ByteBuffer actionBuffer = ByteBuffer.allocate(11); // Action is exactly 11 bytes + ByteBuffer actionBuffer = ByteBuffer.allocate(12); // Action is exactly 12 bytes while (this.running.get()) { actionBuffer.clear(); // Read exactly 11 bytes for one Action int totalBytesRead = 0; - while (totalBytesRead < 11) { + while (totalBytesRead < 12) { int bytesRead = clientSocket.read(actionBuffer); if (bytesRead == -1) { // Client disconnected @@ -236,6 +236,7 @@ private void sendObservationImmediate(Observation observation, SocketChannel cli private void processAction(ByteBuffer actionBuffer) { final Action action = Action.fromBytes(actionBuffer.array()); DataBridge.getInstance().setLatestAction(action); + LOGGER.info("Action received: {}", action); } private void cleanup() { diff --git a/test_action_client.py b/test_action_client.py new file mode 100644 index 0000000..5be844b --- /dev/null +++ b/test_action_client.py @@ -0,0 +1,645 @@ +#!/usr/bin/env python3 +""" +Simple test client for debugging action communication with the Minecraft Forge mod. + +This client connects to the Unix domain sockets created by the MVI Minecraft mod +and allows you to send test actions and receive observations for debugging. +""" + +import socket +import struct +import time +import threading +from dataclasses import dataclass +from typing import Optional +import argparse +import sys + + +@dataclass +class ActionState: + """Represents the state of all persistent actions (keys that can be held down)""" + up: bool = False + down: bool = False + left: bool = False + right: bool = False + jump: bool = False + sneak: bool = False + sprint: bool = False + inventory: bool = False + drop: bool = False + swap: bool = False + use: bool = False + attack: bool = False + pick_item: bool = False + hotbar1: bool = False + hotbar2: bool = False + hotbar3: bool = False + hotbar4: bool = False + hotbar5: bool = False + hotbar6: bool = False + hotbar7: bool = False + hotbar8: bool = False + + def to_bytes(self) -> bytes: + """Convert action state to 3-byte packed format""" + # First byte: up, down, left, right, jump, sneak, sprint, inventory + first_byte = 0 + first_byte |= (self.up << 7) + first_byte |= (self.down << 6) + first_byte |= (self.left << 5) + first_byte |= (self.right << 4) + first_byte |= (self.jump << 3) + first_byte |= (self.sneak << 2) + first_byte |= (self.sprint << 1) + first_byte |= self.inventory + + # Second byte: drop, swap, use, attack, pick_item, hotbar1, hotbar2, hotbar3 + second_byte = 0 + second_byte |= (self.drop << 7) + second_byte |= (self.swap << 6) + second_byte |= (self.use << 5) + second_byte |= (self.attack << 4) + second_byte |= (self.pick_item << 3) + second_byte |= (self.hotbar1 << 2) + second_byte |= (self.hotbar2 << 1) + second_byte |= self.hotbar3 + + # Third byte: hotbar4, hotbar5, hotbar6, hotbar7, hotbar8, (3 bits padding) + third_byte = 0 + third_byte |= (self.hotbar4 << 7) + third_byte |= (self.hotbar5 << 6) + third_byte |= (self.hotbar6 << 5) + third_byte |= (self.hotbar7 << 4) + third_byte |= (self.hotbar8 << 3) + # Bits 0-2 are padding + + return bytes([first_byte, second_byte, third_byte]) + + +@dataclass +class Action: + """Complete action including persistent and non-persistent actions""" + action_state: ActionState + exit_menu: bool = False + mouse_control_x: float = 0.0 + mouse_control_y: float = 0.0 + + def to_bytes(self) -> bytes: + """Convert action to 12-byte format for network transmission""" + data = bytearray(12) + + # First 3 bytes: ActionState + action_bytes = self.action_state.to_bytes() + data[0:3] = action_bytes + + # Byte 3: exit_menu flag (bit 7, other bits are padding) + menu_byte = (1 << 7) if self.exit_menu else 0 + data[3] = menu_byte + + # Bytes 4-7: mouse_control_x (float) + mouse_x_bytes = struct.pack('>f', self.mouse_control_x) + data[4:8] = mouse_x_bytes + + # Bytes 8-11: mouse_control_y (float) + mouse_y_bytes = struct.pack('>f', self.mouse_control_y) + data[8:12] = mouse_y_bytes + + return bytes(data) + + +class ActionTestClient: + """Test client for sending actions to the Minecraft mod""" + + def __init__(self, action_socket_path: str = "/tmp/mvi_action.sock"): + self.action_socket_path = action_socket_path + self.action_socket: Optional[socket.socket] = None + self.connected = False + + def connect(self) -> bool: + """Connect to the action socket""" + try: + self.action_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.action_socket.connect(self.action_socket_path) + self.connected = True + print(f"✓ Connected to action socket: {self.action_socket_path}") + return True + except Exception as e: + print(f"✗ Failed to connect to action socket: {e}") + return False + + def disconnect(self): + """Disconnect from the action socket""" + if self.action_socket: + self.action_socket.close() + self.action_socket = None + self.connected = False + print("✓ Disconnected from action socket") + + def send_action(self, action: Action) -> bool: + """Send an action to the mod""" + if not self.connected or not self.action_socket: + print("✗ Not connected to action socket") + return False + + try: + action_bytes = action.to_bytes() + self.action_socket.send(action_bytes) + print(f"✓ Sent action: {len(action_bytes)} bytes") + return True + except Exception as e: + print(f"✗ Failed to send action: {e}") + return False + + +class ObservationTestClient: + """Test client for receiving observations from the Minecraft mod""" + + def __init__(self, observation_socket_path: str = "/tmp/mvi_observation.sock"): + self.observation_socket_path = observation_socket_path + self.observation_socket: Optional[socket.socket] = None + self.connected = False + self.running = False + + def connect(self) -> bool: + """Connect to the observation socket""" + try: + self.observation_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.observation_socket.connect(self.observation_socket_path) + self.connected = True + print(f"✓ Connected to observation socket: {self.observation_socket_path}") + return True + except Exception as e: + print(f"✗ Failed to connect to observation socket: {e}") + return False + + def disconnect(self): + """Disconnect from the observation socket""" + self.running = False + if self.observation_socket: + self.observation_socket.close() + self.observation_socket = None + self.connected = False + print("✓ Disconnected from observation socket") + + def receive_observations(self): + """Continuously receive and print observation info""" + if not self.connected or not self.observation_socket: + print("✗ Not connected to observation socket") + return + + self.running = True + observation_count = 0 + + try: + while self.running: + # Read reward (8 bytes, double) + reward_data = self._read_exact(8) + if not reward_data: + break + reward = struct.unpack('>d', reward_data)[0] + + # Read frame length (4 bytes, int) + length_data = self._read_exact(4) + if not length_data: + break + frame_length = struct.unpack('>I', length_data)[0] + + # Read frame data + frame_data = self._read_exact(frame_length) + if not frame_data: + break + + observation_count += 1 + print(f"✓ Observation #{observation_count}: reward={reward:.3f}, frame={frame_length} bytes") + + except Exception as e: + print(f"✗ Error receiving observations: {e}") + + def _read_exact(self, n: int) -> Optional[bytes]: + """Read exactly n bytes from the socket""" + if not self.observation_socket: + return None + + data = b'' + while len(data) < n: + try: + chunk = self.observation_socket.recv(n - len(data)) + if not chunk: + return None + data += chunk + except Exception: + return None + return data + + +def show_help(): + """Display comprehensive help information""" + print("\n" + "=" * 60) + print("MVI ACTION TEST CLIENT - COMMAND REFERENCE") + print("=" * 60) + print("\nMOVEMENT COMMANDS (Auto-held until 'release'):") + print(" w, forward - Move forward") + print(" s, back - Move backward") + print(" a, left - Move left") + print(" d, right - Move right") + print(" space, jump - Jump") + print(" shift, sneak - Sneak/crouch") + print(" ctrl, sprint - Sprint") + print() + print("INTERACTION COMMANDS:") + print(" attack, lmb - Attack/left mouse button (auto-held)") + print(" use, rmb - Use/right mouse button (auto-held)") + print(" e, inventory - Open inventory (instant)") + print(" q, drop - Drop item (instant)") + print(" f, swap - Swap offhand (instant)") + print(" pick - Pick block (instant)") + print() + print("HOTBAR COMMANDS:") + print(" 1-8 - Select hotbar slots 1-8") + print(" hotbar1-8 - Select hotbar slots (alternative)") + print() + print("MOUSE COMMANDS:") + print(" mouse - Move mouse by x,y amount") + print(" turn - Turn horizontally by x amount") + print(" look - Look vertically by y amount") + print(" mleft, mright - Quick turn left/right") + print(" mup, mdown - Quick look up/down") + print() + print("SPECIAL COMMANDS:") + print(" esc, exit - Exit menu/GUI") + print(" hold - Explicitly hold action (for non-auto-held actions)") + print(" release - Release all held actions") + print(" combo - Execute multiple actions together") + print() + print("UTILITY COMMANDS:") + print(" help, h, ? - Show this help") + print(" status - Show connection status") + print(" test - Run quick test sequence") + print(" clear - Clear screen") + print(" quit, exit, q - Exit program") + print("\nEXAMPLES:") + print(" combo w space - Move forward and jump") + print(" hold shift - Start sneaking (no auto-release)") + print(" mouse -10 5 - Turn left 10 units, look up 5 units") + print(" turn 45 - Turn right 45 degrees") + print("=" * 60) + + +def parse_command(command_line: str) -> tuple[ActionState, bool, float, float, bool]: + """ + Parse command line input and return action components + Returns: (action_state, exit_menu, mouse_x, mouse_y, hold_action) + """ + parts = command_line.strip().lower().split() + if not parts: + return ActionState(), False, 0.0, 0.0, False + + main_command = parts[0] + action_state = ActionState() + exit_menu = False + mouse_x, mouse_y = 0.0, 0.0 + hold_action = False + + # Movement and persistent actions that should auto-hold + persistent_actions = { + 'w', 'forward', 's', 'back', 'a', 'left', 'd', 'right', + 'space', 'jump', 'shift', 'sneak', 'ctrl', 'sprint', + 'attack', 'lmb', 'use', 'rmb' + } + + # Handle special commands first + if main_command in ["help", "h", "?"]: + show_help() + return ActionState(), False, 0.0, 0.0, False + + if main_command == "combo" and len(parts) > 1: + # Execute multiple actions together + for action_name in parts[1:]: + _apply_action_to_state(action_state, action_name) + return action_state, exit_menu, mouse_x, mouse_y, False + + if main_command == "hold" and len(parts) > 1: + _apply_action_to_state(action_state, parts[1]) + return action_state, exit_menu, mouse_x, mouse_y, True + + if main_command == "mouse" and len(parts) >= 3: + try: + mouse_x = float(parts[1]) + mouse_y = float(parts[2]) + except ValueError: + print("✗ Invalid mouse coordinates. Use: mouse ") + return action_state, exit_menu, mouse_x, mouse_y, False + + if main_command == "turn" and len(parts) >= 2: + try: + mouse_x = float(parts[1]) + except ValueError: + print("✗ Invalid turn amount. Use: turn ") + return action_state, exit_menu, mouse_x, mouse_y, False + + if main_command == "look" and len(parts) >= 2: + try: + mouse_y = float(parts[1]) + except ValueError: + print("✗ Invalid look amount. Use: look ") + return action_state, exit_menu, mouse_x, mouse_y, False + + # Handle regular single actions + _apply_action_to_state(action_state, main_command) + + # Auto-hold persistent actions + if main_command in persistent_actions: + hold_action = True + + # Handle special cases + if main_command in ["esc", "exit"]: + exit_menu = True + elif main_command == "mleft": + mouse_x = -10.0 + elif main_command == "mright": + mouse_x = 10.0 + elif main_command == "mup": + mouse_y = -10.0 + elif main_command == "mdown": + mouse_y = 10.0 + + return action_state, exit_menu, mouse_x, mouse_y, hold_action + + +def _apply_action_to_state(action_state: ActionState, action_name: str): + """Apply a single action to the action state""" + action_name = action_name.lower() + + # Movement + if action_name in ["w", "forward"]: + action_state.up = True + elif action_name in ["s", "back"]: + action_state.down = True + elif action_name in ["a", "left"]: + action_state.left = True + elif action_name in ["d", "right"]: + action_state.right = True + elif action_name in ["space", "jump"]: + action_state.jump = True + elif action_name in ["shift", "sneak"]: + action_state.sneak = True + elif action_name in ["ctrl", "sprint"]: + action_state.sprint = True + + # Interactions + elif action_name in ["attack", "lmb"]: + action_state.attack = True + elif action_name in ["use", "rmb"]: + action_state.use = True + elif action_name in ["e", "inventory"]: + action_state.inventory = True + elif action_name in ["q", "drop"]: + action_state.drop = True + elif action_name in ["f", "swap"]: + action_state.swap = True + elif action_name == "pick": + action_state.pick_item = True + + # Hotbar + elif action_name in ["1", "hotbar1"]: + action_state.hotbar1 = True + elif action_name in ["2", "hotbar2"]: + action_state.hotbar2 = True + elif action_name in ["3", "hotbar3"]: + action_state.hotbar3 = True + elif action_name in ["4", "hotbar4"]: + action_state.hotbar4 = True + elif action_name in ["5", "hotbar5"]: + action_state.hotbar5 = True + elif action_name in ["6", "hotbar6"]: + action_state.hotbar6 = True + elif action_name in ["7", "hotbar7"]: + action_state.hotbar7 = True + elif action_name in ["8", "hotbar8"]: + action_state.hotbar8 = True + + +def run_interactive_mode(): + """Run enhanced interactive mode for testing actions""" + action_client = ActionTestClient() + observation_client = ObservationTestClient() + held_actions = ActionState() # Track held actions + + print("MVI Action Test Client - Interactive Mode") + print("=" * 50) + + # Connect to action socket + if not action_client.connect(): + return + + # Optionally connect to observation socket + print("\nDo you want to monitor observations? (y/n): ", end="") + if input().lower().startswith('y'): + if observation_client.connect(): + obs_thread = threading.Thread(target=observation_client.receive_observations, daemon=True) + obs_thread.start() + + print("\nInteractive Action Testing Started") + print("Movement commands (w/a/s/d/space/shift/ctrl) and attack/use are auto-held") + print("Use 'release' to stop all held actions") + print("Type 'help' for command reference, 'quit' to exit") + print("-" * 50) + + try: + while True: + try: + command_line = input("Action> ").strip() + + if not command_line: + continue + + # Handle special meta commands + if command_line.lower() in ["quit", "exit", "q"]: + break + elif command_line.lower() == "status": + print(f"✓ Action socket: {'Connected' if action_client.connected else 'Disconnected'}") + print(f"✓ Observation socket: {'Connected' if observation_client.connected else 'Disconnected'}") + continue + elif command_line.lower() == "clear": + print("\033[2J\033[H") # Clear screen + continue + elif command_line.lower() == "release": + # Release all held actions + held_actions = ActionState() + release_action = Action(ActionState()) + action_client.send_action(release_action) + print("✓ Released all held actions") + continue + elif command_line.lower() == "test": + print("Running quick test sequence...") + test_sequence = ["w", "a", "s", "d", "space", "attack"] + for test_cmd in test_sequence: + print(f" Testing: {test_cmd}") + action_state, exit_menu, mouse_x, mouse_y, _ = parse_command(test_cmd) + action = Action(action_state, exit_menu, mouse_x, mouse_y) + action_client.send_action(action) + time.sleep(0.2) + # Auto-release for test + action_client.send_action(Action(ActionState())) + time.sleep(0.1) + print("✓ Test sequence completed") + continue + + # Parse the command + action_state, exit_menu, mouse_x, mouse_y, hold_action = parse_command(command_line) + + # Skip if it was just a help command + if command_line.lower() in ["help", "h", "?"]: + continue + + # Combine with held actions + if hold_action: + # Add new action to held actions + held_actions.up |= action_state.up + held_actions.down |= action_state.down + held_actions.left |= action_state.left + held_actions.right |= action_state.right + held_actions.jump |= action_state.jump + held_actions.sneak |= action_state.sneak + held_actions.sprint |= action_state.sprint + held_actions.inventory |= action_state.inventory + held_actions.drop |= action_state.drop + held_actions.swap |= action_state.swap + held_actions.use |= action_state.use + held_actions.attack |= action_state.attack + held_actions.pick_item |= action_state.pick_item + held_actions.hotbar1 |= action_state.hotbar1 + held_actions.hotbar2 |= action_state.hotbar2 + held_actions.hotbar3 |= action_state.hotbar3 + held_actions.hotbar4 |= action_state.hotbar4 + held_actions.hotbar5 |= action_state.hotbar5 + held_actions.hotbar6 |= action_state.hotbar6 + held_actions.hotbar7 |= action_state.hotbar7 + held_actions.hotbar8 |= action_state.hotbar8 + + final_action_state = held_actions + print("✓ Action held (use 'release' to stop)") + else: + # Combine one-time action with held actions + final_action_state = ActionState( + up=action_state.up or held_actions.up, + down=action_state.down or held_actions.down, + left=action_state.left or held_actions.left, + right=action_state.right or held_actions.right, + jump=action_state.jump or held_actions.jump, + sneak=action_state.sneak or held_actions.sneak, + sprint=action_state.sprint or held_actions.sprint, + inventory=action_state.inventory or held_actions.inventory, + drop=action_state.drop or held_actions.drop, + swap=action_state.swap or held_actions.swap, + use=action_state.use or held_actions.use, + attack=action_state.attack or held_actions.attack, + pick_item=action_state.pick_item or held_actions.pick_item, + hotbar1=action_state.hotbar1 or held_actions.hotbar1, + hotbar2=action_state.hotbar2 or held_actions.hotbar2, + hotbar3=action_state.hotbar3 or held_actions.hotbar3, + hotbar4=action_state.hotbar4 or held_actions.hotbar4, + hotbar5=action_state.hotbar5 or held_actions.hotbar5, + hotbar6=action_state.hotbar6 or held_actions.hotbar6, + hotbar7=action_state.hotbar7 or held_actions.hotbar7, + hotbar8=action_state.hotbar8 or held_actions.hotbar8, + ) + + # Send the action + action = Action(final_action_state, exit_menu, mouse_x, mouse_y) + success = action_client.send_action(action) + + if success and not hold_action: + # Auto-release after short delay for one-time actions + time.sleep(0.1) + release_action = Action(held_actions) # Keep only held actions + action_client.send_action(release_action) + + except EOFError: + print("\nEOF received, exiting...") + break + except Exception as e: + print(f"✗ Error processing command: {e}") + + except KeyboardInterrupt: + print("\nInterrupted by user") + finally: + # Release all actions before disconnecting + if action_client.connected: + action_client.send_action(Action(ActionState())) + action_client.disconnect() + observation_client.disconnect() + + +def run_automated_test(): + """Run automated test sequence""" + action_client = ActionTestClient() + + print("MVI Automated Action Test") + print("=" * 50) + + if not action_client.connect(): + return + + try: + test_actions = [ + ("Forward", ActionState(up=True)), + ("Back", ActionState(down=True)), + ("Left", ActionState(left=True)), + ("Right", ActionState(right=True)), + ("Jump", ActionState(jump=True)), + ("Sneak", ActionState(sneak=True)), + ("Sprint", ActionState(sprint=True)), + ("Attack", ActionState(attack=True)), + ("Use", ActionState(use=True)), + ("Hotbar 1", ActionState(hotbar1=True)), + ("Turn Left", ActionState(), False, -10.0, 0.0), + ("Turn Right", ActionState(), False, 10.0, 0.0), + ("Exit Menu", ActionState(), True, 0.0, 0.0), + ] + + print("Running automated test sequence...") + for i, test_data in enumerate(test_actions): + name = test_data[0] + action_state = test_data[1] + exit_menu = test_data[2] if len(test_data) > 2 else False + mouse_x = test_data[3] if len(test_data) > 3 else 0.0 + mouse_y = test_data[4] if len(test_data) > 4 else 0.0 + + print(f" {i+1:2d}. {name}") + action = Action(action_state, exit_menu, mouse_x, mouse_y) + action_client.send_action(action) + time.sleep(0.5) + + # Send release + release_action = Action(ActionState()) + action_client.send_action(release_action) + time.sleep(0.5) + + print("✓ Automated test completed successfully") + + except KeyboardInterrupt: + print("\nTest interrupted by user") + finally: + action_client.disconnect() + + +def main(): + parser = argparse.ArgumentParser(description="MVI Action Test Client") + parser.add_argument("--auto", action="store_true", help="Run automated test sequence") + parser.add_argument("--action-socket", default="/tmp/mvi_action.sock", + help="Path to action socket (default: /tmp/mvi_action.sock)") + parser.add_argument("--obs-socket", default="/tmp/mvi_observation.sock", + help="Path to observation socket (default: /tmp/mvi_observation.sock)") + + args = parser.parse_args() + + if args.auto: + run_automated_test() + else: + run_interactive_mode() + + +if __name__ == "__main__": + main() \ No newline at end of file From 9960c5727fbff51887c534f45247d80fc172e340 Mon Sep 17 00:00:00 2001 From: thomashopkins32 Date: Sat, 2 Aug 2025 17:22:43 -0400 Subject: [PATCH 07/21] Remove hold logic on test client --- test_action_client.py | 142 +++++++----------------------------------- 1 file changed, 22 insertions(+), 120 deletions(-) diff --git a/test_action_client.py b/test_action_client.py index 5be844b..e5e7032 100644 --- a/test_action_client.py +++ b/test_action_client.py @@ -238,7 +238,7 @@ def show_help(): print("\n" + "=" * 60) print("MVI ACTION TEST CLIENT - COMMAND REFERENCE") print("=" * 60) - print("\nMOVEMENT COMMANDS (Auto-held until 'release'):") + print("\nMOVEMENT COMMANDS:") print(" w, forward - Move forward") print(" s, back - Move backward") print(" a, left - Move left") @@ -248,12 +248,12 @@ def show_help(): print(" ctrl, sprint - Sprint") print() print("INTERACTION COMMANDS:") - print(" attack, lmb - Attack/left mouse button (auto-held)") - print(" use, rmb - Use/right mouse button (auto-held)") - print(" e, inventory - Open inventory (instant)") - print(" q, drop - Drop item (instant)") - print(" f, swap - Swap offhand (instant)") - print(" pick - Pick block (instant)") + print(" attack, lmb - Attack/left mouse button") + print(" use, rmb - Use/right mouse button") + print(" e, inventory - Open inventory") + print(" q, drop - Drop item") + print(" f, swap - Swap offhand") + print(" pick - Pick block") print() print("HOTBAR COMMANDS:") print(" 1-8 - Select hotbar slots 1-8") @@ -268,8 +268,6 @@ def show_help(): print() print("SPECIAL COMMANDS:") print(" esc, exit - Exit menu/GUI") - print(" hold - Explicitly hold action (for non-auto-held actions)") - print(" release - Release all held actions") print(" combo - Execute multiple actions together") print() print("UTILITY COMMANDS:") @@ -280,48 +278,35 @@ def show_help(): print(" quit, exit, q - Exit program") print("\nEXAMPLES:") print(" combo w space - Move forward and jump") - print(" hold shift - Start sneaking (no auto-release)") print(" mouse -10 5 - Turn left 10 units, look up 5 units") print(" turn 45 - Turn right 45 degrees") print("=" * 60) -def parse_command(command_line: str) -> tuple[ActionState, bool, float, float, bool]: +def parse_command(command_line: str) -> tuple[ActionState, bool, float, float]: """ Parse command line input and return action components - Returns: (action_state, exit_menu, mouse_x, mouse_y, hold_action) + Returns: (action_state, exit_menu, mouse_x, mouse_y) """ parts = command_line.strip().lower().split() if not parts: - return ActionState(), False, 0.0, 0.0, False + return ActionState(), False, 0.0, 0.0 main_command = parts[0] action_state = ActionState() exit_menu = False mouse_x, mouse_y = 0.0, 0.0 - hold_action = False - - # Movement and persistent actions that should auto-hold - persistent_actions = { - 'w', 'forward', 's', 'back', 'a', 'left', 'd', 'right', - 'space', 'jump', 'shift', 'sneak', 'ctrl', 'sprint', - 'attack', 'lmb', 'use', 'rmb' - } # Handle special commands first if main_command in ["help", "h", "?"]: show_help() - return ActionState(), False, 0.0, 0.0, False + return ActionState(), False, 0.0, 0.0 if main_command == "combo" and len(parts) > 1: # Execute multiple actions together for action_name in parts[1:]: _apply_action_to_state(action_state, action_name) - return action_state, exit_menu, mouse_x, mouse_y, False - - if main_command == "hold" and len(parts) > 1: - _apply_action_to_state(action_state, parts[1]) - return action_state, exit_menu, mouse_x, mouse_y, True + return action_state, exit_menu, mouse_x, mouse_y if main_command == "mouse" and len(parts) >= 3: try: @@ -329,29 +314,25 @@ def parse_command(command_line: str) -> tuple[ActionState, bool, float, float, b mouse_y = float(parts[2]) except ValueError: print("✗ Invalid mouse coordinates. Use: mouse ") - return action_state, exit_menu, mouse_x, mouse_y, False + return action_state, exit_menu, mouse_x, mouse_y if main_command == "turn" and len(parts) >= 2: try: mouse_x = float(parts[1]) except ValueError: print("✗ Invalid turn amount. Use: turn ") - return action_state, exit_menu, mouse_x, mouse_y, False + return action_state, exit_menu, mouse_x, mouse_y if main_command == "look" and len(parts) >= 2: try: mouse_y = float(parts[1]) except ValueError: print("✗ Invalid look amount. Use: look ") - return action_state, exit_menu, mouse_x, mouse_y, False + return action_state, exit_menu, mouse_x, mouse_y # Handle regular single actions _apply_action_to_state(action_state, main_command) - # Auto-hold persistent actions - if main_command in persistent_actions: - hold_action = True - # Handle special cases if main_command in ["esc", "exit"]: exit_menu = True @@ -364,7 +345,7 @@ def parse_command(command_line: str) -> tuple[ActionState, bool, float, float, b elif main_command == "mdown": mouse_y = 10.0 - return action_state, exit_menu, mouse_x, mouse_y, hold_action + return action_state, exit_menu, mouse_x, mouse_y def _apply_action_to_state(action_state: ActionState, action_name: str): @@ -424,7 +405,6 @@ def run_interactive_mode(): """Run enhanced interactive mode for testing actions""" action_client = ActionTestClient() observation_client = ObservationTestClient() - held_actions = ActionState() # Track held actions print("MVI Action Test Client - Interactive Mode") print("=" * 50) @@ -441,8 +421,6 @@ def run_interactive_mode(): obs_thread.start() print("\nInteractive Action Testing Started") - print("Movement commands (w/a/s/d/space/shift/ctrl) and attack/use are auto-held") - print("Use 'release' to stop all held actions") print("Type 'help' for command reference, 'quit' to exit") print("-" * 50) @@ -464,97 +442,29 @@ def run_interactive_mode(): elif command_line.lower() == "clear": print("\033[2J\033[H") # Clear screen continue - elif command_line.lower() == "release": - # Release all held actions - held_actions = ActionState() - release_action = Action(ActionState()) - action_client.send_action(release_action) - print("✓ Released all held actions") - continue + elif command_line.lower() == "test": print("Running quick test sequence...") test_sequence = ["w", "a", "s", "d", "space", "attack"] for test_cmd in test_sequence: print(f" Testing: {test_cmd}") - action_state, exit_menu, mouse_x, mouse_y, _ = parse_command(test_cmd) + action_state, exit_menu, mouse_x, mouse_y = parse_command(test_cmd) action = Action(action_state, exit_menu, mouse_x, mouse_y) action_client.send_action(action) - time.sleep(0.2) - # Auto-release for test - action_client.send_action(Action(ActionState())) - time.sleep(0.1) + time.sleep(0.3) print("✓ Test sequence completed") continue # Parse the command - action_state, exit_menu, mouse_x, mouse_y, hold_action = parse_command(command_line) + action_state, exit_menu, mouse_x, mouse_y = parse_command(command_line) # Skip if it was just a help command if command_line.lower() in ["help", "h", "?"]: continue - # Combine with held actions - if hold_action: - # Add new action to held actions - held_actions.up |= action_state.up - held_actions.down |= action_state.down - held_actions.left |= action_state.left - held_actions.right |= action_state.right - held_actions.jump |= action_state.jump - held_actions.sneak |= action_state.sneak - held_actions.sprint |= action_state.sprint - held_actions.inventory |= action_state.inventory - held_actions.drop |= action_state.drop - held_actions.swap |= action_state.swap - held_actions.use |= action_state.use - held_actions.attack |= action_state.attack - held_actions.pick_item |= action_state.pick_item - held_actions.hotbar1 |= action_state.hotbar1 - held_actions.hotbar2 |= action_state.hotbar2 - held_actions.hotbar3 |= action_state.hotbar3 - held_actions.hotbar4 |= action_state.hotbar4 - held_actions.hotbar5 |= action_state.hotbar5 - held_actions.hotbar6 |= action_state.hotbar6 - held_actions.hotbar7 |= action_state.hotbar7 - held_actions.hotbar8 |= action_state.hotbar8 - - final_action_state = held_actions - print("✓ Action held (use 'release' to stop)") - else: - # Combine one-time action with held actions - final_action_state = ActionState( - up=action_state.up or held_actions.up, - down=action_state.down or held_actions.down, - left=action_state.left or held_actions.left, - right=action_state.right or held_actions.right, - jump=action_state.jump or held_actions.jump, - sneak=action_state.sneak or held_actions.sneak, - sprint=action_state.sprint or held_actions.sprint, - inventory=action_state.inventory or held_actions.inventory, - drop=action_state.drop or held_actions.drop, - swap=action_state.swap or held_actions.swap, - use=action_state.use or held_actions.use, - attack=action_state.attack or held_actions.attack, - pick_item=action_state.pick_item or held_actions.pick_item, - hotbar1=action_state.hotbar1 or held_actions.hotbar1, - hotbar2=action_state.hotbar2 or held_actions.hotbar2, - hotbar3=action_state.hotbar3 or held_actions.hotbar3, - hotbar4=action_state.hotbar4 or held_actions.hotbar4, - hotbar5=action_state.hotbar5 or held_actions.hotbar5, - hotbar6=action_state.hotbar6 or held_actions.hotbar6, - hotbar7=action_state.hotbar7 or held_actions.hotbar7, - hotbar8=action_state.hotbar8 or held_actions.hotbar8, - ) - # Send the action - action = Action(final_action_state, exit_menu, mouse_x, mouse_y) - success = action_client.send_action(action) - - if success and not hold_action: - # Auto-release after short delay for one-time actions - time.sleep(0.1) - release_action = Action(held_actions) # Keep only held actions - action_client.send_action(release_action) + action = Action(action_state, exit_menu, mouse_x, mouse_y) + action_client.send_action(action) except EOFError: print("\nEOF received, exiting...") @@ -565,9 +475,6 @@ def run_interactive_mode(): except KeyboardInterrupt: print("\nInterrupted by user") finally: - # Release all actions before disconnecting - if action_client.connected: - action_client.send_action(Action(ActionState())) action_client.disconnect() observation_client.disconnect() @@ -611,11 +518,6 @@ def run_automated_test(): action = Action(action_state, exit_menu, mouse_x, mouse_y) action_client.send_action(action) time.sleep(0.5) - - # Send release - release_action = Action(ActionState()) - action_client.send_action(release_action) - time.sleep(0.5) print("✓ Automated test completed successfully") From 60d62dc7ef689da4c769215057341e257acd2cfc Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sun, 3 Aug 2025 21:00:38 -0400 Subject: [PATCH 08/21] Single click actions; Started inventory control --- .../java/com/mvi/mvimod/ActionHandler.java | 82 ++++++++++++++++++- .../com/mvi/mvimod/ClientEventHandler.java | 43 +++++----- 2 files changed, 101 insertions(+), 24 deletions(-) diff --git a/forge/src/main/java/com/mvi/mvimod/ActionHandler.java b/forge/src/main/java/com/mvi/mvimod/ActionHandler.java index 071af80..3e57c41 100644 --- a/forge/src/main/java/com/mvi/mvimod/ActionHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ActionHandler.java @@ -3,7 +3,10 @@ import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.KeyMapping; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import org.slf4j.Logger; +import org.lwjgl.glfw.GLFW; public class ActionHandler { private static final Logger LOGGER = LogUtils.getLogger(); @@ -35,10 +38,18 @@ public static ActionState getActionState(Minecraft mc) { return actionState; } + public static void clickKeyMapping(KeyMapping keyMapping, boolean click) { + if (click) { + KeyMapping.click(keyMapping.getKey()); + } + } + public static void pressKeyMapping(KeyMapping keyMapping, boolean down) { if (down && !keyMapping.isDown()) { + LOGGER.info("Pressing key mapping: {}", keyMapping.getName()); keyMapping.setDown(true); } else if (!down && keyMapping.isDown()) { + LOGGER.info("Releasing key mapping: {}", keyMapping.getName()); keyMapping.setDown(false); } } @@ -50,8 +61,77 @@ public static void exitMenu(Minecraft mc, boolean exit) { } public static void turnPlayer(Minecraft mc, float deltaX, float deltaY) { - if (deltaX != 0.0f && deltaY != 0.0f) { + if (deltaX != 0.0f || deltaY != 0.0f) { mc.player.turn(deltaX, deltaY); } } + + /** + * Enhanced mouse control that handles both world and GUI interaction + */ + public static void handleMouseControl(Minecraft mc, float deltaX, float deltaY) { + if (isScreenOpen(mc)) { + // Handle GUI mouse movement + moveMouseInGUI(mc, deltaX, deltaY); + } else { + // Handle world mouse movement (player turning) + turnPlayer(mc, deltaX, deltaY); + } + } + + /** + * Check if any screen is currently open that the agent can observe + */ + public static boolean isScreenOpen(Minecraft mc) { + return mc.screen != null; + } + + /** + * Move mouse cursor within GUI screens + */ + public static void moveMouseInGUI(Minecraft mc, float deltaX, float deltaY) { + if (mc.screen != null) { + long windowHandle = mc.getWindow().getWindow(); + + // Get current mouse position + double[] currentX = new double[1]; + double[] currentY = new double[1]; + GLFW.glfwGetCursorPos(windowHandle, currentX, currentY); + + // Calculate new position + double newX = currentX[0] + deltaX; + double newY = currentY[0] + deltaY; + + // Clamp to screen bounds + int screenWidth = mc.getWindow().getScreenWidth(); + int screenHeight = mc.getWindow().getScreenHeight(); + newX = Math.max(0, Math.min(screenWidth - 1, newX)); + newY = Math.max(0, Math.min(screenHeight - 1, newY)); + + // Set new mouse position + GLFW.glfwSetCursorPos(windowHandle, newX, newY); + + LOGGER.debug("GUI Mouse moved by ({}, {}) to ({}, {})", deltaX, deltaY, newX, newY); + } + } + + public static void clickMouse(Minecraft mc, boolean leftClick) { + if (mc.screen != null) { + long windowHandle = mc.getWindow().getWindow(); + + // Get current mouse position + double[] mouseX = new double[1]; + double[] mouseY = new double[1]; + GLFW.glfwGetCursorPos(windowHandle, mouseX, mouseY); + + int button = leftClick ? GLFW.GLFW_MOUSE_BUTTON_LEFT : GLFW.GLFW_MOUSE_BUTTON_RIGHT; + + // Simulate mouse press and release + mc.screen.mouseClicked(mouseX[0], mouseY[0], button); + mc.screen.mouseReleased(mouseX[0], mouseY[0], button); + + LOGGER.debug("GUI Mouse {} clicked at ({}, {})", + leftClick ? "left" : "right", mouseX[0], mouseY[0]); + } + } } diff --git a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java index 93671b1..da0999f 100644 --- a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java @@ -36,39 +36,36 @@ private static void processAction(Action action) { ActionHandler.pressKeyMapping(mc.options.keyJump, action.jump()); ActionHandler.pressKeyMapping(mc.options.keyShift, action.sneak()); ActionHandler.pressKeyMapping(mc.options.keySprint, action.sprint()); - ActionHandler.pressKeyMapping(mc.options.keyInventory, action.inventory()); - ActionHandler.pressKeyMapping(mc.options.keyDrop, action.drop()); - ActionHandler.pressKeyMapping(mc.options.keySwapOffhand, action.swap()); - ActionHandler.pressKeyMapping(mc.options.keyUse, action.use()); - ActionHandler.pressKeyMapping(mc.options.keyAttack, action.attack()); - ActionHandler.pressKeyMapping(mc.options.keyPickItem, action.pick_item()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[0], action.hotbar1()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[1], action.hotbar2()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[2], action.hotbar3()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[3], action.hotbar4()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[4], action.hotbar5()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[5], action.hotbar6()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[6], action.hotbar7()); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[7], action.hotbar8()); + ActionHandler.clickKeyMapping(mc.options.keyInventory, action.inventory()); + ActionHandler.clickKeyMapping(mc.options.keyDrop, action.drop()); + ActionHandler.clickKeyMapping(mc.options.keySwapOffhand, action.swap()); + ActionHandler.clickKeyMapping(mc.options.keyUse, action.use()); + ActionHandler.clickKeyMapping(mc.options.keyAttack, action.attack()); + ActionHandler.clickKeyMapping(mc.options.keyPickItem, action.pick_item()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[0], action.hotbar1()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[1], action.hotbar2()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[2], action.hotbar3()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[3], action.hotbar4()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[4], action.hotbar5()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[5], action.hotbar6()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[6], action.hotbar7()); + ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[7], action.hotbar8()); ActionHandler.exitMenu(mc, action.exitMenu()); - ActionHandler.turnPlayer(mc, action.mouseControlX(), action.mouseControlY()); + ActionHandler.handleMouseControl(mc, action.mouseControlX(), action.mouseControlY()); } @SubscribeEvent public static void onClientTick(TickEvent.ClientTickEvent event) { Minecraft mc = Minecraft.getInstance(); - if (mc.level != null && mc.player != null) { + if (mc.level != null && mc.player != null && event.phase == TickEvent.Phase.END) { final Action action = dataBridge.getLatestAction(); if (action != null) { processAction(action); } - - if (event.phase == TickEvent.Phase.END) { - // int reward = packageReward(); - final ActionState currentActionState = ActionHandler.getActionState(Minecraft.getInstance()); - final byte[] frame = captureFrame(); - dataBridge.sendObservation(new Observation(0.0, currentActionState, frame)); - } + // int reward = packageReward(); + final ActionState currentActionState = ActionHandler.getActionState(Minecraft.getInstance()); + final byte[] frame = captureFrame(); + dataBridge.sendObservation(new Observation(0.0, currentActionState, frame)); } } From 6dbd6e64667bbd5a58b9dae97f60171ad82ad67d Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 9 Aug 2025 17:13:58 -0400 Subject: [PATCH 09/21] Added mouse clicks; Mouse control in GUI is still needed --- .../src/main/java/com/mvi/mvimod/Action.java | 6 +- .../java/com/mvi/mvimod/ActionHandler.java | 135 +++++++++++------- .../main/java/com/mvi/mvimod/ActionState.java | 14 +- .../com/mvi/mvimod/ClientEventHandler.java | 2 + .../src/main/java/com/mvi/mvimod/Config.java | 12 ++ test_action_client.py | 38 +++++ 6 files changed, 151 insertions(+), 56 deletions(-) diff --git a/forge/src/main/java/com/mvi/mvimod/Action.java b/forge/src/main/java/com/mvi/mvimod/Action.java index 3536114..bd8d004 100644 --- a/forge/src/main/java/com/mvi/mvimod/Action.java +++ b/forge/src/main/java/com/mvi/mvimod/Action.java @@ -45,6 +45,8 @@ public record Action( public boolean hotbar6() { return actionState.hotbar6(); } public boolean hotbar7() { return actionState.hotbar7(); } public boolean hotbar8() { return actionState.hotbar8(); } + public boolean rightMouseDown() { return actionState.rightMouseDown(); } + public boolean leftMouseDown() { return actionState.leftMouseDown(); } public byte[] toBytes() { ByteBuffer buffer = ByteBuffer.allocate(3 + 1 + 4 + 4); @@ -92,13 +94,15 @@ public Action( boolean drop, boolean swap, boolean use, boolean attack, boolean pick_item, boolean hotbar1, boolean hotbar2, boolean hotbar3, boolean hotbar4, boolean hotbar5, boolean hotbar6, boolean hotbar7, boolean hotbar8, + boolean rightMouseDown, boolean leftMouseDown, boolean exitMenu, float mouseControlX, float mouseControlY ) { this( new ActionState( up, down, left, right, jump, sneak, sprint, inventory, drop, swap, use, attack, pick_item, - hotbar1, hotbar2, hotbar3, hotbar4, hotbar5, hotbar6, hotbar7, hotbar8 + hotbar1, hotbar2, hotbar3, hotbar4, hotbar5, hotbar6, hotbar7, hotbar8, + rightMouseDown, leftMouseDown ), exitMenu, mouseControlX, mouseControlY ); diff --git a/forge/src/main/java/com/mvi/mvimod/ActionHandler.java b/forge/src/main/java/com/mvi/mvimod/ActionHandler.java index 3e57c41..78c3624 100644 --- a/forge/src/main/java/com/mvi/mvimod/ActionHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ActionHandler.java @@ -3,13 +3,17 @@ import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.KeyMapping; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import org.slf4j.Logger; import org.lwjgl.glfw.GLFW; public class ActionHandler { private static final Logger LOGGER = LogUtils.getLogger(); + private static boolean rightMouseDown = false; + private static boolean leftMouseDown = false; + private static boolean virtualMouseInitialized = false; + private static double virtualMouseX = 0.0; + private static double virtualMouseY = 0.0; + private static boolean suppressSystemMouseInput = true; public static ActionState getActionState(Minecraft mc) { final ActionState actionState = new ActionState( @@ -33,7 +37,9 @@ public static ActionState getActionState(Minecraft mc) { mc.options.keyHotbarSlots[4].isDown(), mc.options.keyHotbarSlots[5].isDown(), mc.options.keyHotbarSlots[6].isDown(), - mc.options.keyHotbarSlots[7].isDown() + mc.options.keyHotbarSlots[7].isDown(), + rightMouseDown, + leftMouseDown ); return actionState; } @@ -66,72 +72,97 @@ public static void turnPlayer(Minecraft mc, float deltaX, float deltaY) { } } - /** - * Enhanced mouse control that handles both world and GUI interaction - */ public static void handleMouseControl(Minecraft mc, float deltaX, float deltaY) { if (isScreenOpen(mc)) { - // Handle GUI mouse movement moveMouseInGUI(mc, deltaX, deltaY); } else { - // Handle world mouse movement (player turning) + // Re-enable OS cursor when not in GUI + if (suppressSystemMouseInput) { + long windowHandle = mc.getWindow().getWindow(); + GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); + } turnPlayer(mc, deltaX, deltaY); } } - /** - * Check if any screen is currently open that the agent can observe - */ public static boolean isScreenOpen(Minecraft mc) { return mc.screen != null; } - /** - * Move mouse cursor within GUI screens - */ public static void moveMouseInGUI(Minecraft mc, float deltaX, float deltaY) { if (mc.screen != null) { - long windowHandle = mc.getWindow().getWindow(); - - // Get current mouse position - double[] currentX = new double[1]; - double[] currentY = new double[1]; - GLFW.glfwGetCursorPos(windowHandle, currentX, currentY); - - // Calculate new position - double newX = currentX[0] + deltaX; - double newY = currentY[0] + deltaY; - - // Clamp to screen bounds - int screenWidth = mc.getWindow().getScreenWidth(); - int screenHeight = mc.getWindow().getScreenHeight(); - newX = Math.max(0, Math.min(screenWidth - 1, newX)); - newY = Math.max(0, Math.min(screenHeight - 1, newY)); - - // Set new mouse position - GLFW.glfwSetCursorPos(windowHandle, newX, newY); - - LOGGER.debug("GUI Mouse moved by ({}, {}) to ({}, {})", deltaX, deltaY, newX, newY); + // Detach OS mouse by disabling the cursor when suppressing system input + suppressSystemMouseInput = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); + if (suppressSystemMouseInput) { + long windowHandle = mc.getWindow().getWindow(); + GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } + // Initialize virtual mouse at window center if needed + if (!virtualMouseInitialized) { + virtualMouseX = mc.getWindow().getWidth() / 2.0; + virtualMouseY = mc.getWindow().getHeight() / 2.0; + virtualMouseInitialized = true; + } + + // Update internal virtual mouse position + virtualMouseX += deltaX; + virtualMouseY += deltaY; + + // Clamp to window bounds + double maxX = mc.getWindow().getWidth() - 1; + double maxY = mc.getWindow().getHeight() - 1; + if (virtualMouseX < 0) virtualMouseX = 0; + if (virtualMouseY < 0) virtualMouseY = 0; + if (virtualMouseX > maxX) virtualMouseX = maxX; + if (virtualMouseY > maxY) virtualMouseY = maxY; + + // Notify current screen about mouse movement without moving OS cursor + mc.screen.mouseMoved(virtualMouseX, virtualMouseY); + LOGGER.debug("GUI Virtual mouse moved by ({}, {}) to ({}, {})", deltaX, deltaY, virtualMouseX, virtualMouseY); } } - public static void clickMouse(Minecraft mc, boolean leftClick) { - if (mc.screen != null) { - long windowHandle = mc.getWindow().getWindow(); - - // Get current mouse position - double[] mouseX = new double[1]; - double[] mouseY = new double[1]; - GLFW.glfwGetCursorPos(windowHandle, mouseX, mouseY); - - int button = leftClick ? GLFW.GLFW_MOUSE_BUTTON_LEFT : GLFW.GLFW_MOUSE_BUTTON_RIGHT; - - // Simulate mouse press and release - mc.screen.mouseClicked(mouseX[0], mouseY[0], button); - mc.screen.mouseReleased(mouseX[0], mouseY[0], button); - - LOGGER.debug("GUI Mouse {} clicked at ({}, {})", - leftClick ? "left" : "right", mouseX[0], mouseY[0]); + public static void rightMouseControl(Minecraft mc, boolean down) { + if (mc.screen != null && rightMouseDown != down) { + suppressSystemMouseInput = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); + if (suppressSystemMouseInput) { + long windowHandle = mc.getWindow().getWindow(); + GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } + + int button = GLFW.GLFW_MOUSE_BUTTON_RIGHT; + + if (down) { + mc.screen.mouseClicked(virtualMouseX, virtualMouseY, button); + rightMouseDown = true; + } else { + mc.screen.mouseReleased(virtualMouseX, virtualMouseY, button); + rightMouseDown = false; + } + + LOGGER.debug("GUI Virtual right click at ({}, {})", virtualMouseX, virtualMouseY); + } + } + + public static void leftMouseControl(Minecraft mc, boolean down) { + if (mc.screen != null && leftMouseDown != down) { + suppressSystemMouseInput = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); + if (suppressSystemMouseInput) { + long windowHandle = mc.getWindow().getWindow(); + GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } + + int button = GLFW.GLFW_MOUSE_BUTTON_LEFT; + + if (down) { + mc.screen.mouseClicked(virtualMouseX, virtualMouseY, button); + leftMouseDown = true; + } else { + mc.screen.mouseReleased(virtualMouseX, virtualMouseY, button); + leftMouseDown = false; + } + + LOGGER.debug("GUI Virtual left click at ({}, {})", virtualMouseX, virtualMouseY); } } } diff --git a/forge/src/main/java/com/mvi/mvimod/ActionState.java b/forge/src/main/java/com/mvi/mvimod/ActionState.java index 4501096..a3475a5 100644 --- a/forge/src/main/java/com/mvi/mvimod/ActionState.java +++ b/forge/src/main/java/com/mvi/mvimod/ActionState.java @@ -23,7 +23,9 @@ public record ActionState( boolean hotbar5, boolean hotbar6, boolean hotbar7, - boolean hotbar8 + boolean hotbar8, + boolean rightMouseDown, + boolean leftMouseDown ) { public byte[] toBytes() { ByteBuffer buffer = ByteBuffer.allocate(3); @@ -74,7 +76,11 @@ public byte[] toBytes() { thirdByte |= (hotbar7 ? 1 : 0); thirdByte <<= 1; thirdByte |= (hotbar8 ? 1 : 0); - // Leave 3 bits unused for padding + thirdByte <<= 1; + thirdByte |= (rightMouseDown ? 1 : 0); + thirdByte <<= 1; + thirdByte |= (leftMouseDown ? 1 : 0); + // Leave 1 bit unused for padding buffer.put((byte) thirdByte); return buffer.array(); @@ -108,7 +114,9 @@ public static ActionState fromBytes(byte[] bytes) { ((thirdByte >> 6) & 1) == 1, // hotbar5 ((thirdByte >> 5) & 1) == 1, // hotbar6 ((thirdByte >> 4) & 1) == 1, // hotbar7 - ((thirdByte >> 3) & 1) == 1 // hotbar8 + ((thirdByte >> 3) & 1) == 1, // hotbar8 + ((thirdByte >> 2) & 1) == 1, // rightMouseDown + ((thirdByte >> 1) & 1) == 1 // leftMouseDown ); } } diff --git a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java index da0999f..0ff40e3 100644 --- a/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java +++ b/forge/src/main/java/com/mvi/mvimod/ClientEventHandler.java @@ -52,6 +52,8 @@ private static void processAction(Action action) { ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[7], action.hotbar8()); ActionHandler.exitMenu(mc, action.exitMenu()); ActionHandler.handleMouseControl(mc, action.mouseControlX(), action.mouseControlY()); + ActionHandler.rightMouseControl(mc, action.rightMouseDown()); + ActionHandler.leftMouseControl(mc, action.leftMouseDown()); } @SubscribeEvent diff --git a/forge/src/main/java/com/mvi/mvimod/Config.java b/forge/src/main/java/com/mvi/mvimod/Config.java index 956180b..eb1a412 100644 --- a/forge/src/main/java/com/mvi/mvimod/Config.java +++ b/forge/src/main/java/com/mvi/mvimod/Config.java @@ -17,6 +17,7 @@ public class Config { public static final ForgeConfigSpec.ConfigValue JPEG_QUALITY; public static final ForgeConfigSpec.ConfigValue MAX_FRAME_SIZE; public static final ForgeConfigSpec.ConfigValue CHANGE_THRESHOLD; + public static final ForgeConfigSpec.ConfigValue SUPPRESS_SYSTEM_MOUSE_INPUT; // Built Configuration Specification public static final ForgeConfigSpec SPEC; @@ -73,6 +74,17 @@ public class Config { BUILDER.pop(); + // Input Configuration + BUILDER.comment("Input Configuration"); + BUILDER.push("input"); + + SUPPRESS_SYSTEM_MOUSE_INPUT = + BUILDER + .comment("If true, disables OS cursor while a GUI is open and uses a virtual mouse for agent control") + .define("suppress_system_mouse_input", true); + + BUILDER.pop(); + // Build the specification after all values are defined SPEC = BUILDER.build(); } diff --git a/test_action_client.py b/test_action_client.py index e5e7032..87e139d 100644 --- a/test_action_client.py +++ b/test_action_client.py @@ -40,6 +40,8 @@ class ActionState: hotbar6: bool = False hotbar7: bool = False hotbar8: bool = False + right_mouse_down: bool = False + left_mouse_down: bool = False def to_bytes(self) -> bytes: """Convert action state to 3-byte packed format""" @@ -72,6 +74,8 @@ def to_bytes(self) -> bytes: third_byte |= (self.hotbar6 << 5) third_byte |= (self.hotbar7 << 4) third_byte |= (self.hotbar8 << 3) + third_byte |= (self.right_mouse_down << 2) + third_byte |= (self.left_mouse_down << 1) # Bits 0-2 are padding return bytes([first_byte, second_byte, third_byte]) @@ -265,6 +269,9 @@ def show_help(): print(" look - Look vertically by y amount") print(" mleft, mright - Quick turn left/right") print(" mup, mdown - Quick look up/down") + print(" lclick, rclick - Quick left/right mouse click") + print(" mouseleft - Hold or release left mouse (GUI)") + print(" mouseright - Hold or release right mouse (GUI)") print() print("SPECIAL COMMANDS:") print(" esc, exit - Exit menu/GUI") @@ -329,6 +336,23 @@ def parse_command(command_line: str) -> tuple[ActionState, bool, float, float]: except ValueError: print("✗ Invalid look amount. Use: look ") return action_state, exit_menu, mouse_x, mouse_y + + # Mouse button control (GUI) - explicit down/up + if main_command in ["mouseleft", "mouseright"]: + state = None + if len(parts) >= 2: + if parts[1] in ["down", "press", "on"]: + state = True + elif parts[1] in ["up", "release", "off"]: + state = False + # Default to down if not specified + if state is None: + state = True + if main_command == "mouseleft": + action_state.left_mouse_down = state + else: + action_state.right_mouse_down = state + return action_state, exit_menu, mouse_x, mouse_y # Handle regular single actions _apply_action_to_state(action_state, main_command) @@ -454,6 +478,20 @@ def run_interactive_mode(): time.sleep(0.3) print("✓ Test sequence completed") continue + + # Quick mouse click helpers + if command_line.lower() in ["lclick", "rclick"]: + is_left = command_line.lower() == "lclick" + # Press + press_state = ActionState(left_mouse_down=True) if is_left else ActionState(right_mouse_down=True) + action = Action(press_state, False, 0.0, 0.0) + action_client.send_action(action) + time.sleep(0.05) + # Release + release_state = ActionState(left_mouse_down=False, right_mouse_down=False) + action = Action(release_state, False, 0.0, 0.0) + action_client.send_action(action) + continue # Parse the command action_state, exit_menu, mouse_x, mouse_y = parse_command(command_line) From 924a53bb0f328ce971dac67cd3c9232c2c5cbc23 Mon Sep 17 00:00:00 2001 From: thomashopkins32 Date: Sat, 10 Jan 2026 16:13:26 -0500 Subject: [PATCH 10/21] Update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 42341a2..825a468 100644 --- a/.gitignore +++ b/.gitignore @@ -199,3 +199,6 @@ run # Files from Forge MDK forge*changelog.txt forge/run-data/ + +# Cursor +.cursor/ \ No newline at end of file From 5cd01dd263538917a5f037859c77d13174997be4 Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sun, 11 Jan 2026 16:58:26 -0500 Subject: [PATCH 11/21] Add RawInput spec for keyboard, mouse, and text usage --- forge/src/main/java/com/mineagent/Action.java | 110 ---------------- .../main/java/com/mineagent/ActionState.java | 122 ------------------ .../java/com/mineagent/InputInjector.java | 0 .../src/main/java/com/mineagent/RawInput.java | 45 +++++++ 4 files changed, 45 insertions(+), 232 deletions(-) delete mode 100644 forge/src/main/java/com/mineagent/Action.java delete mode 100644 forge/src/main/java/com/mineagent/ActionState.java create mode 100644 forge/src/main/java/com/mineagent/InputInjector.java create mode 100644 forge/src/main/java/com/mineagent/RawInput.java diff --git a/forge/src/main/java/com/mineagent/Action.java b/forge/src/main/java/com/mineagent/Action.java deleted file mode 100644 index bd8d004..0000000 --- a/forge/src/main/java/com/mineagent/Action.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.mvi.mvimod; - -import java.nio.ByteBuffer; -import com.mojang.logging.LogUtils; -import org.slf4j.Logger; - -/* - * This class represents the complete action schema for communication between the client and server. - * It contains both persistent actions (button states that continue until released) and - * non-persistent actions (like mouse movement and menu commands). - * - * The format is as follows: - * - Persistent actions (3 bytes) - see ActionState for bit layout - * - EXIT_MENU (1 bit) - * - PADDING (7 bits) - * - Mouse control X (4 bytes) - * - Mouse control Y (4 bytes) - */ -public record Action( - ActionState actionState, - boolean exitMenu, - float mouseControlX, - float mouseControlY -) { - private static final Logger LOGGER = LogUtils.getLogger(); - - public boolean up() { return actionState.up(); } - public boolean down() { return actionState.down(); } - public boolean left() { return actionState.left(); } - public boolean right() { return actionState.right(); } - public boolean jump() { return actionState.jump(); } - public boolean sneak() { return actionState.sneak(); } - public boolean sprint() { return actionState.sprint(); } - public boolean inventory() { return actionState.inventory(); } - public boolean drop() { return actionState.drop(); } - public boolean swap() { return actionState.swap(); } - public boolean use() { return actionState.use(); } - public boolean attack() { return actionState.attack(); } - public boolean pick_item() { return actionState.pick_item(); } - public boolean hotbar1() { return actionState.hotbar1(); } - public boolean hotbar2() { return actionState.hotbar2(); } - public boolean hotbar3() { return actionState.hotbar3(); } - public boolean hotbar4() { return actionState.hotbar4(); } - public boolean hotbar5() { return actionState.hotbar5(); } - public boolean hotbar6() { return actionState.hotbar6(); } - public boolean hotbar7() { return actionState.hotbar7(); } - public boolean hotbar8() { return actionState.hotbar8(); } - public boolean rightMouseDown() { return actionState.rightMouseDown(); } - public boolean leftMouseDown() { return actionState.leftMouseDown(); } - - public byte[] toBytes() { - ByteBuffer buffer = ByteBuffer.allocate(3 + 1 + 4 + 4); - - // Add persistent action bytes - byte[] persistentBytes = actionState.toBytes(); - buffer.put(persistentBytes); - - // Add exit menu flag with padding - int menuByte = 0; - menuByte |= (exitMenu ? 1 << 7 : 0); - buffer.put((byte) menuByte); - - // Add mouse controls - buffer.putFloat(mouseControlX); - buffer.putFloat(mouseControlY); - - return buffer.array(); - } - - public static Action fromBytes(byte[] bytes) { - LOGGER.info("Action fromBytes: {}", bytes); - ByteBuffer buffer = ByteBuffer.wrap(bytes); - - // Extract persistent action bytes - byte[] persistentBytes = new byte[3]; - buffer.get(persistentBytes); - ActionState actionState = ActionState.fromBytes(persistentBytes); - - // Extract exit menu flag - int menuByte = buffer.get() & 0xFF; - boolean exitMenu = ((menuByte >> 7) & 1) == 1; - - // Extract mouse controls - float mouseControlX = buffer.getFloat(); - float mouseControlY = buffer.getFloat(); - - return new Action(actionState, exitMenu, mouseControlX, mouseControlY); - } - - // Convenience constructor for creating actions with individual persistent fields - public Action( - boolean up, boolean down, boolean left, boolean right, - boolean jump, boolean sneak, boolean sprint, boolean inventory, - boolean drop, boolean swap, boolean use, boolean attack, boolean pick_item, - boolean hotbar1, boolean hotbar2, boolean hotbar3, boolean hotbar4, - boolean hotbar5, boolean hotbar6, boolean hotbar7, boolean hotbar8, - boolean rightMouseDown, boolean leftMouseDown, - boolean exitMenu, float mouseControlX, float mouseControlY - ) { - this( - new ActionState( - up, down, left, right, jump, sneak, sprint, inventory, - drop, swap, use, attack, pick_item, - hotbar1, hotbar2, hotbar3, hotbar4, hotbar5, hotbar6, hotbar7, hotbar8, - rightMouseDown, leftMouseDown - ), - exitMenu, mouseControlX, mouseControlY - ); - } -} \ No newline at end of file diff --git a/forge/src/main/java/com/mineagent/ActionState.java b/forge/src/main/java/com/mineagent/ActionState.java deleted file mode 100644 index a3475a5..0000000 --- a/forge/src/main/java/com/mineagent/ActionState.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.mvi.mvimod; - -import java.nio.ByteBuffer; - -public record ActionState( - boolean up, - boolean down, - boolean left, - boolean right, - boolean jump, - boolean sneak, - boolean sprint, - boolean inventory, - boolean drop, - boolean swap, - boolean use, - boolean attack, - boolean pick_item, - boolean hotbar1, - boolean hotbar2, - boolean hotbar3, - boolean hotbar4, - boolean hotbar5, - boolean hotbar6, - boolean hotbar7, - boolean hotbar8, - boolean rightMouseDown, - boolean leftMouseDown -) { - public byte[] toBytes() { - ByteBuffer buffer = ByteBuffer.allocate(3); - - int firstByte = 0; - firstByte |= (up ? 1 : 0); - firstByte <<= 1; - firstByte |= (down ? 1 : 0); - firstByte <<= 1; - firstByte |= (left ? 1 : 0); - firstByte <<= 1; - firstByte |= (right ? 1 : 0); - firstByte <<= 1; - firstByte |= (jump ? 1 : 0); - firstByte <<= 1; - firstByte |= (sneak ? 1 : 0); - firstByte <<= 1; - firstByte |= (sprint ? 1 : 0); - firstByte <<= 1; - firstByte |= (inventory ? 1 : 0); - buffer.put((byte) firstByte); - - int secondByte = 0; - secondByte |= (drop ? 1 : 0); - secondByte <<= 1; - secondByte |= (swap ? 1 : 0); - secondByte <<= 1; - secondByte |= (use ? 1 : 0); - secondByte <<= 1; - secondByte |= (attack ? 1 : 0); - secondByte <<= 1; - secondByte |= (pick_item ? 1 : 0); - secondByte <<= 1; - secondByte |= (hotbar1 ? 1 : 0); - secondByte <<= 1; - secondByte |= (hotbar2 ? 1 : 0); - secondByte <<= 1; - secondByte |= (hotbar3 ? 1 : 0); - buffer.put((byte) secondByte); - - int thirdByte = 0; - thirdByte |= (hotbar4 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar5 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar6 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar7 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (hotbar8 ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (rightMouseDown ? 1 : 0); - thirdByte <<= 1; - thirdByte |= (leftMouseDown ? 1 : 0); - // Leave 1 bit unused for padding - buffer.put((byte) thirdByte); - - return buffer.array(); - } - - public static ActionState fromBytes(byte[] bytes) { - ByteBuffer buffer = ByteBuffer.wrap(bytes); - - int firstByte = buffer.get() & 0xFF; - int secondByte = buffer.get() & 0xFF; - int thirdByte = buffer.get() & 0xFF; - - return new ActionState( - ((firstByte >> 7) & 1) == 1, // up - ((firstByte >> 6) & 1) == 1, // down - ((firstByte >> 5) & 1) == 1, // left - ((firstByte >> 4) & 1) == 1, // right - ((firstByte >> 3) & 1) == 1, // jump - ((firstByte >> 2) & 1) == 1, // sneak - ((firstByte >> 1) & 1) == 1, // sprint - ((firstByte >> 0) & 1) == 1, // inventory - ((secondByte >> 7) & 1) == 1, // drop - ((secondByte >> 6) & 1) == 1, // swap - ((secondByte >> 5) & 1) == 1, // use - ((secondByte >> 4) & 1) == 1, // attack - ((secondByte >> 3) & 1) == 1, // pick_item - ((secondByte >> 2) & 1) == 1, // hotbar1 - ((secondByte >> 1) & 1) == 1, // hotbar2 - ((secondByte >> 0) & 1) == 1, // hotbar3 - ((thirdByte >> 7) & 1) == 1, // hotbar4 - ((thirdByte >> 6) & 1) == 1, // hotbar5 - ((thirdByte >> 5) & 1) == 1, // hotbar6 - ((thirdByte >> 4) & 1) == 1, // hotbar7 - ((thirdByte >> 3) & 1) == 1, // hotbar8 - ((thirdByte >> 2) & 1) == 1, // rightMouseDown - ((thirdByte >> 1) & 1) == 1 // leftMouseDown - ); - } -} diff --git a/forge/src/main/java/com/mineagent/InputInjector.java b/forge/src/main/java/com/mineagent/InputInjector.java new file mode 100644 index 0000000..e69de29 diff --git a/forge/src/main/java/com/mineagent/RawInput.java b/forge/src/main/java/com/mineagent/RawInput.java new file mode 100644 index 0000000..cfc2ede --- /dev/null +++ b/forge/src/main/java/com/mineagent/RawInput.java @@ -0,0 +1,45 @@ +package com.mineagent; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +public record RawInput( + int[] keyCodes, + float mouseDx, + float mouseDy, + byte mouseButtons, + float scrollDelta, + String text +) { + public static RawInput fromBytes(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + // Keys + int numKeysPressed = buffer.get() & 0xFF; + int[] keyCodes = new int[numKeysPressed]; + for (int i = 0; i < numKeysPressed; i++) { + keyCodes[i] = buffer.getShort(); + } + + // Mouse + float mouseDx = buffer.getFloat(); + float mouseDy = buffer.getFloat(); + byte mouseButtons = buffer.get(); + float scrollDelta = buffer.getFloat(); + + // Text (for typing) + int textLength = buffer.getShort() & 0xFFFF; + byte[] textBytes = new byte[textLength]; + buffer.get(textBytes); + String text = new String(textBytes, StandardCharsets.UTF_8); + + return new RawInput( + keyCodes, + mouseDx, + mouseDy, + mouseButtons, + scrollDelta, + text + ); + } +}; From 0277cdf0f8bf8b10a4a08839860dc1a67dc64436 Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sun, 11 Jan 2026 17:09:20 -0500 Subject: [PATCH 12/21] Add unit tests for RawInput record --- forge/build.gradle | 4 + .../test/java/com/mineagent/RawInputTest.java | 260 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 forge/src/test/java/com/mineagent/RawInputTest.java diff --git a/forge/build.gradle b/forge/build.gradle index 718e591..fbf1750 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -128,6 +128,10 @@ dependencies { // then special handling is done to allow a setup of a vanilla dependency without the use of an external repository. minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + // JUnit 5 for unit testing + testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // Example mod dependency with JEI // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime // compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}" diff --git a/forge/src/test/java/com/mineagent/RawInputTest.java b/forge/src/test/java/com/mineagent/RawInputTest.java new file mode 100644 index 0000000..1dc2322 --- /dev/null +++ b/forge/src/test/java/com/mineagent/RawInputTest.java @@ -0,0 +1,260 @@ +package com.mineagent; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class RawInputTest { + + /** + * Helper method to create a byte array representing a RawInput. + * + *

Binary format: + * + *

    + *
  • 1 byte: number of keys pressed (unsigned) + *
  • N × 2 bytes: key codes as shorts + *
  • 4 bytes: mouseDx as float + *
  • 4 bytes: mouseDy as float + *
  • 1 byte: mouseButtons + *
  • 4 bytes: scrollDelta as float + *
  • 2 bytes: text length (unsigned short) + *
  • M bytes: UTF-8 encoded text + *
+ */ + private byte[] createRawInputBytes( + int[] keyCodes, + float mouseDx, + float mouseDy, + byte mouseButtons, + float scrollDelta, + String text) { + byte[] textBytes = text.getBytes(StandardCharsets.UTF_8); + int bufferSize = 1 + (keyCodes.length * 2) + 4 + 4 + 1 + 4 + 2 + textBytes.length; + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + + buffer.put((byte) keyCodes.length); + for (int keyCode : keyCodes) { + buffer.putShort((short) keyCode); + } + buffer.putFloat(mouseDx); + buffer.putFloat(mouseDy); + buffer.put(mouseButtons); + buffer.putFloat(scrollDelta); + buffer.putShort((short) textBytes.length); + buffer.put(textBytes); + + return buffer.array(); + } + + @Test + void fromBytes_withTypicalInput_parsesCorrectly() { + int[] keyCodes = {87, 32}; // W key and Space + float mouseDx = 10.5f; + float mouseDy = -5.25f; + byte mouseButtons = 0b00000001; // Left button pressed + float scrollDelta = 1.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(mouseDx, result.mouseDx(), 0.0001f); + assertEquals(mouseDy, result.mouseDy(), 0.0001f); + assertEquals(mouseButtons, result.mouseButtons()); + assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withNoKeysPressed_parsesEmptyKeyArray() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(0, result.keyCodes().length); + } + + @Test + void fromBytes_withMultipleKeys_parsesAllKeys() { + int[] keyCodes = {87, 65, 83, 68, 340}; // WASD + Left Shift + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(5, result.keyCodes().length); + } + + @Test + void fromBytes_withNegativeMouseDeltas_parsesCorrectly() { + int[] keyCodes = {}; + float mouseDx = -100.75f; + float mouseDy = -200.5f; + byte mouseButtons = 0; + float scrollDelta = -3.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(mouseDx, result.mouseDx(), 0.0001f); + assertEquals(mouseDy, result.mouseDy(), 0.0001f); + assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); + } + + @Test + void fromBytes_withAllMouseButtonsPressed_parsesCorrectly() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = (byte) 0xFF; // All buttons pressed + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals((byte) 0xFF, result.mouseButtons()); + } + + @Test + void fromBytes_withText_parsesTextCorrectly() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = "Hello World!"; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withUnicodeText_parsesUtf8Correctly() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = "日本語テスト 🎮"; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withCompleteInput_parsesAllFieldsCorrectly() { + int[] keyCodes = {69, 256}; // E key and Escape + float mouseDx = 42.0f; + float mouseDy = -17.5f; + byte mouseButtons = 0b00000101; // Left and middle buttons + float scrollDelta = 2.5f; + String text = "test command"; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(mouseDx, result.mouseDx(), 0.0001f); + assertEquals(mouseDy, result.mouseDy(), 0.0001f); + assertEquals(mouseButtons, result.mouseButtons()); + assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withMaxKeyCount_parsesAllKeys() { + // Test with 255 keys (maximum for unsigned byte) + int[] keyCodes = new int[255]; + for (int i = 0; i < 255; i++) { + keyCodes[i] = i; + } + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(255, result.keyCodes().length); + for (int i = 0; i < 255; i++) { + assertEquals(i, result.keyCodes()[i]); + } + } + + @Test + void fromBytes_withNegativeKeyCode_parsesAsSignedShort() { + // Key codes are stored as shorts, so negative values are possible + int[] keyCodes = {-1, -100}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(-1, result.keyCodes()[0]); + assertEquals(-100, result.keyCodes()[1]); + } + + @Test + void fromBytes_withLargeFloatValues_parsesCorrectly() { + int[] keyCodes = {}; + float mouseDx = Float.MAX_VALUE; + float mouseDy = Float.MIN_VALUE; + byte mouseButtons = 0; + float scrollDelta = Float.POSITIVE_INFINITY; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(Float.MAX_VALUE, result.mouseDx(), 0.0f); + assertEquals(Float.MIN_VALUE, result.mouseDy(), 0.0f); + assertEquals(Float.POSITIVE_INFINITY, result.scrollDelta(), 0.0f); + } + + @Test + void recordEquality_withSameValues_areEqual() { + int[] keyCodes = {65}; + RawInput input1 = new RawInput(keyCodes, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + RawInput input2 = new RawInput(keyCodes, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + + // Records use reference equality for arrays, so these won't be equal + // unless they share the same array reference + assertEquals(input1, input2); + } + + @Test + void recordEquality_withDifferentValues_areNotEqual() { + RawInput input1 = new RawInput(new int[] {65}, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + RawInput input2 = new RawInput(new int[] {66}, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + + assertNotEquals(input1, input2); + } +} From 58c1bc64a271fcba0bba9907d85e3b3a93d54dd8 Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sun, 11 Jan 2026 18:44:04 -0500 Subject: [PATCH 13/21] Reworked actions to use keyboard GLFW inputs and mouse movements --- forge/build.gradle | 2 +- .../java/com/mineagent/ActionHandler.java | 168 --- .../com/mineagent/ClientEventHandler.java | 196 ++-- forge/src/main/java/com/mineagent/Config.java | 8 +- .../main/java/com/mineagent/DataBridge.java | 119 +- .../java/com/mineagent/InputInjector.java | 317 +++++ .../java/com/mineagent/NetworkHandler.java | 570 +++++---- .../main/java/com/mineagent/Observation.java | 3 +- .../resources/META-INF/accesstransformer.cfg | 12 + test_action_client.py | 1030 ++++++++++------- 10 files changed, 1488 insertions(+), 937 deletions(-) delete mode 100644 forge/src/main/java/com/mineagent/ActionHandler.java create mode 100644 forge/src/main/resources/META-INF/accesstransformer.cfg diff --git a/forge/build.gradle b/forge/build.gradle index fbf1750..f731666 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -57,7 +57,7 @@ minecraft { // However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge. // This default location is a best practice to automatically put the file in the right place in the final jar. // See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information. - // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') // Default run configurations. // These can be tweaked, removed, or duplicated as needed. diff --git a/forge/src/main/java/com/mineagent/ActionHandler.java b/forge/src/main/java/com/mineagent/ActionHandler.java deleted file mode 100644 index 78c3624..0000000 --- a/forge/src/main/java/com/mineagent/ActionHandler.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.mvi.mvimod; - -import com.mojang.logging.LogUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.KeyMapping; -import org.slf4j.Logger; -import org.lwjgl.glfw.GLFW; - -public class ActionHandler { - private static final Logger LOGGER = LogUtils.getLogger(); - private static boolean rightMouseDown = false; - private static boolean leftMouseDown = false; - private static boolean virtualMouseInitialized = false; - private static double virtualMouseX = 0.0; - private static double virtualMouseY = 0.0; - private static boolean suppressSystemMouseInput = true; - - public static ActionState getActionState(Minecraft mc) { - final ActionState actionState = new ActionState( - mc.options.keyUp.isDown(), - mc.options.keyDown.isDown(), - mc.options.keyLeft.isDown(), - mc.options.keyRight.isDown(), - mc.options.keyJump.isDown(), - mc.options.keyShift.isDown(), - mc.options.keySprint.isDown(), - mc.options.keyInventory.isDown(), - mc.options.keyDrop.isDown(), - mc.options.keySwapOffhand.isDown(), - mc.options.keyUse.isDown(), - mc.options.keyAttack.isDown(), - mc.options.keyPickItem.isDown(), - mc.options.keyHotbarSlots[0].isDown(), - mc.options.keyHotbarSlots[1].isDown(), - mc.options.keyHotbarSlots[2].isDown(), - mc.options.keyHotbarSlots[3].isDown(), - mc.options.keyHotbarSlots[4].isDown(), - mc.options.keyHotbarSlots[5].isDown(), - mc.options.keyHotbarSlots[6].isDown(), - mc.options.keyHotbarSlots[7].isDown(), - rightMouseDown, - leftMouseDown - ); - return actionState; - } - - public static void clickKeyMapping(KeyMapping keyMapping, boolean click) { - if (click) { - KeyMapping.click(keyMapping.getKey()); - } - } - - public static void pressKeyMapping(KeyMapping keyMapping, boolean down) { - if (down && !keyMapping.isDown()) { - LOGGER.info("Pressing key mapping: {}", keyMapping.getName()); - keyMapping.setDown(true); - } else if (!down && keyMapping.isDown()) { - LOGGER.info("Releasing key mapping: {}", keyMapping.getName()); - keyMapping.setDown(false); - } - } - - public static void exitMenu(Minecraft mc, boolean exit) { - if (exit) { - mc.setScreen(null); - } - } - - public static void turnPlayer(Minecraft mc, float deltaX, float deltaY) { - if (deltaX != 0.0f || deltaY != 0.0f) { - mc.player.turn(deltaX, deltaY); - } - } - - public static void handleMouseControl(Minecraft mc, float deltaX, float deltaY) { - if (isScreenOpen(mc)) { - moveMouseInGUI(mc, deltaX, deltaY); - } else { - // Re-enable OS cursor when not in GUI - if (suppressSystemMouseInput) { - long windowHandle = mc.getWindow().getWindow(); - GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); - } - turnPlayer(mc, deltaX, deltaY); - } - } - - public static boolean isScreenOpen(Minecraft mc) { - return mc.screen != null; - } - - public static void moveMouseInGUI(Minecraft mc, float deltaX, float deltaY) { - if (mc.screen != null) { - // Detach OS mouse by disabling the cursor when suppressing system input - suppressSystemMouseInput = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); - if (suppressSystemMouseInput) { - long windowHandle = mc.getWindow().getWindow(); - GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - // Initialize virtual mouse at window center if needed - if (!virtualMouseInitialized) { - virtualMouseX = mc.getWindow().getWidth() / 2.0; - virtualMouseY = mc.getWindow().getHeight() / 2.0; - virtualMouseInitialized = true; - } - - // Update internal virtual mouse position - virtualMouseX += deltaX; - virtualMouseY += deltaY; - - // Clamp to window bounds - double maxX = mc.getWindow().getWidth() - 1; - double maxY = mc.getWindow().getHeight() - 1; - if (virtualMouseX < 0) virtualMouseX = 0; - if (virtualMouseY < 0) virtualMouseY = 0; - if (virtualMouseX > maxX) virtualMouseX = maxX; - if (virtualMouseY > maxY) virtualMouseY = maxY; - - // Notify current screen about mouse movement without moving OS cursor - mc.screen.mouseMoved(virtualMouseX, virtualMouseY); - LOGGER.debug("GUI Virtual mouse moved by ({}, {}) to ({}, {})", deltaX, deltaY, virtualMouseX, virtualMouseY); - } - } - - public static void rightMouseControl(Minecraft mc, boolean down) { - if (mc.screen != null && rightMouseDown != down) { - suppressSystemMouseInput = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); - if (suppressSystemMouseInput) { - long windowHandle = mc.getWindow().getWindow(); - GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - - int button = GLFW.GLFW_MOUSE_BUTTON_RIGHT; - - if (down) { - mc.screen.mouseClicked(virtualMouseX, virtualMouseY, button); - rightMouseDown = true; - } else { - mc.screen.mouseReleased(virtualMouseX, virtualMouseY, button); - rightMouseDown = false; - } - - LOGGER.debug("GUI Virtual right click at ({}, {})", virtualMouseX, virtualMouseY); - } - } - - public static void leftMouseControl(Minecraft mc, boolean down) { - if (mc.screen != null && leftMouseDown != down) { - suppressSystemMouseInput = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); - if (suppressSystemMouseInput) { - long windowHandle = mc.getWindow().getWindow(); - GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - - int button = GLFW.GLFW_MOUSE_BUTTON_LEFT; - - if (down) { - mc.screen.mouseClicked(virtualMouseX, virtualMouseY, button); - leftMouseDown = true; - } else { - mc.screen.mouseReleased(virtualMouseX, virtualMouseY, button); - leftMouseDown = false; - } - - LOGGER.debug("GUI Virtual left click at ({}, {})", virtualMouseX, virtualMouseY); - } - } -} diff --git a/forge/src/main/java/com/mineagent/ClientEventHandler.java b/forge/src/main/java/com/mineagent/ClientEventHandler.java index 3901ee8..fa6327b 100644 --- a/forge/src/main/java/com/mineagent/ClientEventHandler.java +++ b/forge/src/main/java/com/mineagent/ClientEventHandler.java @@ -10,123 +10,105 @@ import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.lwjgl.glfw.GLFW; import org.lwjgl.opengl.GL11; import org.slf4j.Logger; +/** + * Handles client-side game events and coordinates input injection with observations. + */ public class ClientEventHandler { - private static final Logger LOGGER = LogUtils.getLogger(); - private static DataBridge dataBridge = DataBridge.getInstance(); - - @SubscribeEvent - public static void onServerStarting(ServerStartingEvent event) { - LOGGER.info("MineAgent Mod Server Starting - Network handler is managed on client side"); - } - - @SubscribeEvent - public static void onServerStopping(ServerStoppingEvent event) { - LOGGER.info("MineAgent Mod Server Stopping"); - } + private static final Logger LOGGER = LogUtils.getLogger(); + private static final DataBridge dataBridge = DataBridge.getInstance(); + + @SubscribeEvent + public static void onServerStarting(ServerStartingEvent event) { + LOGGER.info("MineAgent Mod Server Starting - Network handler is managed on client side"); + } - private static void processAction(Action action) { - Minecraft mc = Minecraft.getInstance(); - ActionHandler.pressKeyMapping(mc.options.keyUp, action.up()); - ActionHandler.pressKeyMapping(mc.options.keyDown, action.down()); - ActionHandler.pressKeyMapping(mc.options.keyLeft, action.left()); - ActionHandler.pressKeyMapping(mc.options.keyRight, action.right()); - ActionHandler.pressKeyMapping(mc.options.keyJump, action.jump()); - ActionHandler.pressKeyMapping(mc.options.keyShift, action.sneak()); - ActionHandler.pressKeyMapping(mc.options.keySprint, action.sprint()); - ActionHandler.clickKeyMapping(mc.options.keyInventory, action.inventory()); - ActionHandler.clickKeyMapping(mc.options.keyDrop, action.drop()); - ActionHandler.clickKeyMapping(mc.options.keySwapOffhand, action.swap()); - ActionHandler.clickKeyMapping(mc.options.keyUse, action.use()); - ActionHandler.clickKeyMapping(mc.options.keyAttack, action.attack()); - ActionHandler.clickKeyMapping(mc.options.keyPickItem, action.pick_item()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[0], action.hotbar1()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[1], action.hotbar2()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[2], action.hotbar3()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[3], action.hotbar4()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[4], action.hotbar5()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[5], action.hotbar6()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[6], action.hotbar7()); - ActionHandler.clickKeyMapping(mc.options.keyHotbarSlots[7], action.hotbar8()); - ActionHandler.exitMenu(mc, action.exitMenu()); - ActionHandler.handleMouseControl(mc, action.mouseControlX(), action.mouseControlY()); - ActionHandler.rightMouseControl(mc, action.rightMouseDown()); - ActionHandler.leftMouseControl(mc, action.leftMouseDown()); - } + @SubscribeEvent + public static void onServerStopping(ServerStoppingEvent event) { + LOGGER.info("MineAgent Mod Server Stopping"); + } - @SubscribeEvent - public static void onClientTick(TickEvent.ClientTickEvent event) { - Minecraft mc = Minecraft.getInstance(); - if (mc.level != null && mc.player != null && event.phase == TickEvent.Phase.END) { - final Action action = dataBridge.getLatestAction(); - if (action != null) { - processAction(action); - } - // int reward = packageReward(); - final ActionState currentActionState = ActionHandler.getActionState(Minecraft.getInstance()); - final byte[] frame = captureFrame(); - dataBridge.sendObservation(new Observation(0.0, currentActionState, frame)); + /** + * Main game tick handler. Processes raw input and captures observations. + */ + @SubscribeEvent + public static void onClientTick(TickEvent.ClientTickEvent event) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level != null && mc.player != null && event.phase == TickEvent.Phase.END) { + // Handle input suppression when client is connected + handleInputSuppression(mc); + + // Process any pending raw input + final RawInput rawInput = dataBridge.getLatestRawInput(); + if (rawInput != null) { + dataBridge.getInputInjector().inject(rawInput); + } + + // IMPORTANT: Maintain button state every tick for continuous actions + // This fires press events and sets KeyMapping states for held buttons + dataBridge.getInputInjector().maintainButtonState(); + + // Capture and send observation + final byte[] frame = captureFrame(); + dataBridge.sendObservation(new Observation(0.0, frame)); + } + } + + /** + * Handles input suppression when a Python client is connected. + * Disables the system cursor to prevent real mouse input from interfering. + */ + private static void handleInputSuppression(Minecraft mc) { + boolean clientConnected = dataBridge.isClientConnected(); + boolean suppressMouse = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); + boolean suppressKeyboard = Config.SUPPRESS_SYSTEM_KEYBOARD_INPUT.get(); + + if (clientConnected && (suppressMouse || suppressKeyboard)) { + long windowHandle = mc.getWindow().getWindow(); + + // Suppress mouse by hiding/disabling cursor + if (suppressMouse) { + GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } + + // Note: Keyboard suppression would require intercepting at a lower level + // For now, the agent's input will override via the GLFW handlers + } } - } - @SubscribeEvent - public static void onPlayerHurt(LivingHurtEvent event) { - // if (event.getEntity() instanceof Player) { - // dataBridge.sendEvent("PLAYER_HURT", String.valueOf(event.getAmount())); - // } - } + @SubscribeEvent + public static void onPlayerHurt(LivingHurtEvent event) { + // Future: Calculate reward based on damage + // if (event.getEntity() instanceof Player) { + // dataBridge.sendEvent("PLAYER_HURT", String.valueOf(event.getAmount())); + // } + } - @SubscribeEvent - public static void onPlayerDeath(LivingDeathEvent event) { - // if (event.getEntity() instanceof Player) { - // dataBridge.sendEvent("PLAYER_DEATH", "-100.0"); - // } - } - - private static byte[] captureFrame() { - Minecraft mc = Minecraft.getInstance(); - return captureScreenshot(mc.getWindow()); - } + @SubscribeEvent + public static void onPlayerDeath(LivingDeathEvent event) { + // Future: Calculate negative reward on death + // if (event.getEntity() instanceof Player) { + // dataBridge.sendEvent("PLAYER_DEATH", "-100.0"); + // } + } + + private static byte[] captureFrame() { + Minecraft mc = Minecraft.getInstance(); + return captureScreenshot(mc.getWindow()); + } - private static byte[] captureScreenshot(Window window) { - int width = window.getWidth(); - int height = window.getHeight(); + private static byte[] captureScreenshot(Window window) { + int width = window.getWidth(); + int height = window.getHeight(); - ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 3); - GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 3); + GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - return bytes; - } - - /** - * Release all currently pressed keys (for cleanup on disconnect) - */ - public static void releaseAllKeys() { - Minecraft mc = Minecraft.getInstance(); - ActionHandler.pressKeyMapping(mc.options.keyUp, false); - ActionHandler.pressKeyMapping(mc.options.keyDown, false); - ActionHandler.pressKeyMapping(mc.options.keyLeft, false); - ActionHandler.pressKeyMapping(mc.options.keyRight, false); - ActionHandler.pressKeyMapping(mc.options.keyJump, false); - ActionHandler.pressKeyMapping(mc.options.keyShift, false); - ActionHandler.pressKeyMapping(mc.options.keySprint, false); - ActionHandler.pressKeyMapping(mc.options.keyInventory, false); - ActionHandler.pressKeyMapping(mc.options.keyDrop, false); - ActionHandler.pressKeyMapping(mc.options.keySwapOffhand, false); - ActionHandler.pressKeyMapping(mc.options.keyUse, false); - ActionHandler.pressKeyMapping(mc.options.keyAttack, false); - ActionHandler.pressKeyMapping(mc.options.keyPickItem, false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[0], false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[1], false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[2], false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[3], false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[4], false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[5], false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[6], false); - ActionHandler.pressKeyMapping(mc.options.keyHotbarSlots[7], false); - } + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes); + return bytes; + } } diff --git a/forge/src/main/java/com/mineagent/Config.java b/forge/src/main/java/com/mineagent/Config.java index 22c3063..d8002e4 100644 --- a/forge/src/main/java/com/mineagent/Config.java +++ b/forge/src/main/java/com/mineagent/Config.java @@ -18,6 +18,7 @@ public class Config { public static final ForgeConfigSpec.ConfigValue MAX_FRAME_SIZE; public static final ForgeConfigSpec.ConfigValue CHANGE_THRESHOLD; public static final ForgeConfigSpec.ConfigValue SUPPRESS_SYSTEM_MOUSE_INPUT; + public static final ForgeConfigSpec.ConfigValue SUPPRESS_SYSTEM_KEYBOARD_INPUT; // Built Configuration Specification public static final ForgeConfigSpec SPEC; @@ -80,9 +81,14 @@ public class Config { SUPPRESS_SYSTEM_MOUSE_INPUT = BUILDER - .comment("If true, disables OS cursor while a GUI is open and uses a virtual mouse for agent control") + .comment("If true, disables OS cursor when Python client is connected for agent mouse control") .define("suppress_system_mouse_input", true); + SUPPRESS_SYSTEM_KEYBOARD_INPUT = + BUILDER + .comment("If true, agent keyboard input takes priority when Python client is connected") + .define("suppress_system_keyboard_input", true); + BUILDER.pop(); // Build the specification after all values are defined diff --git a/forge/src/main/java/com/mineagent/DataBridge.java b/forge/src/main/java/com/mineagent/DataBridge.java index e379365..f01a2f7 100644 --- a/forge/src/main/java/com/mineagent/DataBridge.java +++ b/forge/src/main/java/com/mineagent/DataBridge.java @@ -1,49 +1,96 @@ package com.mineagent; import com.mojang.logging.LogUtils; -import org.slf4j.Logger; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.slf4j.Logger; +/** + * Central bridge for data exchange between network handler and game events. + * Manages the latest raw input, observations, and connection state. + */ public class DataBridge { - private static final Logger LOGGER = LogUtils.getLogger(); - private static DataBridge instance; - private NetworkHandler networkHandler; - - private final AtomicReference latestAction = new AtomicReference(); - - private DataBridge() {} + private static final Logger LOGGER = LogUtils.getLogger(); + private static DataBridge instance; + + private NetworkHandler networkHandler; + + // Raw input handling + private final AtomicReference latestRawInput = new AtomicReference<>(); + + // Input injection + private final InputInjector inputInjector = new InputInjector(); + + // Connection state for input suppression + private final AtomicBoolean clientConnected = new AtomicBoolean(false); + + private DataBridge() {} - public static DataBridge getInstance() { - if (instance == null) { - instance = new DataBridge(); - LOGGER.info("DataBridge instance created"); + public static synchronized DataBridge getInstance() { + if (instance == null) { + instance = new DataBridge(); + LOGGER.info("DataBridge instance created"); + } + return instance; } - return instance; - } - public void setNetworkHandler(NetworkHandler handler) { - this.networkHandler = handler; - LOGGER.info("NetworkHandler connected to DataBridge"); - } + public void setNetworkHandler(NetworkHandler handler) { + this.networkHandler = handler; + LOGGER.info("NetworkHandler connected to DataBridge"); + } - public void sendObservation(Observation obs) { - if (networkHandler != null) { - // LOGGER.info("DataBridge sending frame data (size: {} bytes)", obs.frame().length); - networkHandler.setLatest(obs.frame(), obs.reward()); - } else { - LOGGER.warn("Cannot send frame - NetworkHandler is null"); - } - } - - public void setLatestAction(Action action) { - latestAction.set(action); - } + /** + * Sends an observation to connected clients. + */ + public void sendObservation(Observation obs) { + if (networkHandler != null) { + networkHandler.setLatest(obs.frame(), obs.reward()); + } else { + LOGGER.warn("Cannot send frame - NetworkHandler is null"); + } + } + + /** + * Sets the latest raw input received from the Python agent. + */ + public void setLatestRawInput(RawInput rawInput) { + latestRawInput.set(rawInput); + } - public Action getLatestAction() { - Action action = latestAction.getAndSet(null); - if (action != null) { - LOGGER.info("DataBridge getting latest action: {}", action); + /** + * Gets and clears the latest raw input. + * Returns null if no new input is available. + */ + public RawInput getLatestRawInput() { + RawInput rawInput = latestRawInput.getAndSet(null); + if (rawInput != null) { + LOGGER.debug("DataBridge getting latest raw input: {} keys", rawInput.keyCodes().length); + } + return rawInput; + } + + /** + * Gets the input injector for injecting raw input into Minecraft. + */ + public InputInjector getInputInjector() { + return inputInjector; + } + + /** + * Sets whether a Python client is connected. + * Used for input suppression. + */ + public void setClientConnected(boolean connected) { + boolean wasConnected = clientConnected.getAndSet(connected); + if (wasConnected != connected) { + LOGGER.info("Client connection state changed: {}", connected ? "CONNECTED" : "DISCONNECTED"); + } + } + + /** + * Returns whether a Python client is currently connected. + */ + public boolean isClientConnected() { + return clientConnected.get(); } - return action; - } } diff --git a/forge/src/main/java/com/mineagent/InputInjector.java b/forge/src/main/java/com/mineagent/InputInjector.java index e69de29..18f2650 100644 --- a/forge/src/main/java/com/mineagent/InputInjector.java +++ b/forge/src/main/java/com/mineagent/InputInjector.java @@ -0,0 +1,317 @@ +package com.mineagent; + +import com.mojang.logging.LogUtils; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import net.minecraft.client.Minecraft; +import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; + +/** + * Injects raw input events directly into Minecraft's GLFW callback handlers. + * This provides a unified architecture where both keyboard and mouse input + * go through the same handlers that real hardware input uses. + */ +public class InputInjector { + private static final Logger LOGGER = LogUtils.getLogger(); + + // Key state tracking for press/release detection + private Set previouslyPressedKeys = new HashSet<>(); + + // Mouse button state tracking (bits: 0=left, 1=right, 2=middle) + private byte previousMouseButtons = 0; + + // Virtual mouse position (absolute coordinates from accumulated deltas) + private double virtualMouseX = 0.0; + private double virtualMouseY = 0.0; + private boolean mouseInitialized = false; + + /** + * Injects a RawInput into Minecraft's input handlers. + * + * @param input The raw input containing key codes, mouse data, and text + */ + public void inject(RawInput input) { + Minecraft mc = Minecraft.getInstance(); + if (mc == null || mc.getWindow() == null) { + LOGGER.warn("Cannot inject input - Minecraft not initialized"); + return; + } + + long window = mc.getWindow().getWindow(); + + // 1. Handle key state changes via KeyboardHandler + handleKeyboardInput(mc, window, input.keyCodes()); + + // 2. Handle mouse movement via MouseHandler.onMove + handleMouseMovement(mc, window, input.mouseDx(), input.mouseDy()); + + // 3. Handle mouse buttons via MouseHandler.onPress + handleMouseButtons(mc, window, input.mouseButtons()); + + // 4. Handle scroll wheel via MouseHandler.onScroll + handleScrollWheel(mc, window, input.scrollDelta()); + + // 5. Handle text input (for chat/signs) + handleTextInput(mc, input.text()); + } + + /** + * Handles keyboard input by detecting press/release transitions and + * calling KeyboardHandler.keyPress() for each event. + */ + private void handleKeyboardInput(Minecraft mc, long window, int[] keyCodes) { + Set currentKeys = Arrays.stream(keyCodes) + .boxed() + .collect(Collectors.toSet()); + + // Release keys that were pressed but are no longer + for (int key : previouslyPressedKeys) { + if (!currentKeys.contains(key)) { + fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE); + } + } + + // Press keys that are newly pressed + for (int key : currentKeys) { + if (!previouslyPressedKeys.contains(key)) { + fireKeyEvent(mc, window, key, GLFW.GLFW_PRESS); + } + } + + previouslyPressedKeys = currentKeys; + } + + /** + * Fires a key event through Minecraft's KeyboardHandler. + */ + private void fireKeyEvent(Minecraft mc, long window, int keyCode, int action) { + int scanCode = GLFW.glfwGetKeyScancode(keyCode); + int modifiers = computeModifiers(); + + LOGGER.debug("Firing key event: keyCode={}, scanCode={}, action={}, mods={}", + keyCode, scanCode, action == GLFW.GLFW_PRESS ? "PRESS" : "RELEASE", modifiers); + + // Call the same handler that GLFW callbacks use + mc.keyboardHandler.keyPress(window, keyCode, scanCode, action, modifiers); + } + + /** + * Computes current modifier key state based on pressed keys. + */ + private int computeModifiers() { + int mods = 0; + if (previouslyPressedKeys.contains(GLFW.GLFW_KEY_LEFT_SHIFT) || + previouslyPressedKeys.contains(GLFW.GLFW_KEY_RIGHT_SHIFT)) { + mods |= GLFW.GLFW_MOD_SHIFT; + } + if (previouslyPressedKeys.contains(GLFW.GLFW_KEY_LEFT_CONTROL) || + previouslyPressedKeys.contains(GLFW.GLFW_KEY_RIGHT_CONTROL)) { + mods |= GLFW.GLFW_MOD_CONTROL; + } + if (previouslyPressedKeys.contains(GLFW.GLFW_KEY_LEFT_ALT) || + previouslyPressedKeys.contains(GLFW.GLFW_KEY_RIGHT_ALT)) { + mods |= GLFW.GLFW_MOD_ALT; + } + return mods; + } + + /** + * Handles mouse movement by directly rotating the player. + * + * MouseHandler.onMove() only works when the mouse is "grabbed" (captured for gameplay), + * so we bypass it and call player.turn() directly, which is what Minecraft ultimately does. + * + * The delta values are in "pixel" units and get scaled by mouse sensitivity. + */ + private void handleMouseMovement(Minecraft mc, long window, float deltaX, float deltaY) { + if (deltaX == 0 && deltaY == 0) { + return; + } + + // Only rotate the player when in-game (not in menus) + if (mc.player == null || mc.screen != null) { + // If in a menu, we could use onMove for menu interaction + if (mc.screen != null) { + if (!mouseInitialized) { + virtualMouseX = mc.getWindow().getWidth() / 2.0; + virtualMouseY = mc.getWindow().getHeight() / 2.0; + mouseInitialized = true; + } + virtualMouseX += deltaX; + virtualMouseY += deltaY; + mc.mouseHandler.onMove(window, virtualMouseX, virtualMouseY); + } + return; + } + + // Get mouse sensitivity from options (default 0.5, range 0-1) + double sensitivity = mc.options.sensitivity().get() * 0.6 + 0.2; + double sensitivityCubed = sensitivity * sensitivity * sensitivity * 8.0; + + // Convert pixel deltas to rotation deltas (matching Minecraft's turnPlayer logic) + // deltaX affects yaw (horizontal), deltaY affects pitch (vertical) + double yawDelta = deltaX * sensitivityCubed; + double pitchDelta = deltaY * sensitivityCubed; + + LOGGER.debug("Mouse move: delta=({}, {}), yaw={}, pitch={}", + deltaX, deltaY, yawDelta, pitchDelta); + + // Directly rotate the player + // turn(yRot, xRot) where yRot is yaw change and xRot is pitch change + mc.player.turn(yawDelta, pitchDelta); + } + + /** + * Handles mouse button state changes. + * + * For continuous actions like mining, we fire press events every tick while held, + * and only fire release events on actual release transitions. + */ + private void handleMouseButtons(Minecraft mc, long window, byte currentButtons) { + int modifiers = computeModifiers(); + + // Check each button (0=left, 1=right, 2=middle) + for (int button = 0; button < 3; button++) { + boolean wasDown = (previousMouseButtons & (1 << button)) != 0; + boolean isDown = (currentButtons & (1 << button)) != 0; + + if (isDown) { + // Fire press event every tick while held (for continuous actions) + LOGGER.debug("Mouse button: button={}, action=PRESS (continuous)", button); + mc.mouseHandler.onPress(window, button, GLFW.GLFW_PRESS, modifiers); + } else if (wasDown) { + // Only fire release when transitioning from down to up + LOGGER.debug("Mouse button: button={}, action=RELEASE", button); + mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, modifiers); + } + } + + previousMouseButtons = currentButtons; + } + + /** + * Handles scroll wheel input by calling MouseHandler.onScroll(). + */ + private void handleScrollWheel(Minecraft mc, long window, float scrollDelta) { + if (scrollDelta == 0) { + return; + } + + LOGGER.debug("Scroll: delta={}", scrollDelta); + + // Call the same handler that GLFW scroll callbacks use + // xOffset is typically 0 for vertical scrolling, yOffset is the scroll amount + mc.mouseHandler.onScroll(window, 0.0, (double) scrollDelta); + } + + /** + * Handles text input for chat, signs, and other text fields. + * Only processes when a screen is open. + */ + private void handleTextInput(Minecraft mc, String text) { + if (text == null || text.isEmpty()) { + return; + } + + if (mc.screen == null) { + LOGGER.debug("Ignoring text input - no screen open: '{}'", text); + return; + } + + LOGGER.debug("Text input: '{}'", text); + + for (char c : text.toCharArray()) { + mc.screen.charTyped(c, 0); + } + } + + /** + * Resets all input state. Call this when disconnecting or cleaning up. + */ + public void reset() { + Minecraft mc = Minecraft.getInstance(); + if (mc != null && mc.getWindow() != null) { + long window = mc.getWindow().getWindow(); + + // Release all pressed keys + for (int key : previouslyPressedKeys) { + fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE); + } + + // Release all pressed mouse buttons + for (int button = 0; button < 3; button++) { + if ((previousMouseButtons & (1 << button)) != 0) { + mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, 0); + } + } + } + + previouslyPressedKeys.clear(); + previousMouseButtons = 0; + mouseInitialized = false; + + LOGGER.info("InputInjector reset"); + } + + /** + * Gets the current virtual mouse X position. + */ + public double getVirtualMouseX() { + return virtualMouseX; + } + + /** + * Gets the current virtual mouse Y position. + */ + public double getVirtualMouseY() { + return virtualMouseY; + } + + /** + * Gets the set of currently pressed key codes. + */ + public Set getPressedKeys() { + return new HashSet<>(previouslyPressedKeys); + } + + /** + * Gets the current mouse button state. + */ + public byte getMouseButtons() { + return previousMouseButtons; + } + + /** + * Maintains continuous button state by firing press events every tick. + * This must be called every tick to simulate holding a mouse button. + */ + public void maintainButtonState() { + Minecraft mc = Minecraft.getInstance(); + if (mc == null || mc.getWindow() == null || mc.mouseHandler == null) { + return; + } + + // If any buttons are held, fire press events to maintain the state + if (previousMouseButtons != 0) { + long window = mc.getWindow().getWindow(); + int modifiers = computeModifiers(); + + for (int button = 0; button < 3; button++) { + if ((previousMouseButtons & (1 << button)) != 0) { + mc.mouseHandler.onPress(window, button, GLFW.GLFW_PRESS, modifiers); + } + } + } + + // Also set KeyMapping states as backup + if (mc.options != null) { + boolean leftDown = (previousMouseButtons & 1) != 0; + boolean rightDown = (previousMouseButtons & 2) != 0; + mc.options.keyAttack.setDown(leftDown); + mc.options.keyUse.setDown(rightDown); + } + } +} diff --git a/forge/src/main/java/com/mineagent/NetworkHandler.java b/forge/src/main/java/com/mineagent/NetworkHandler.java index 1e3f061..8914cba 100644 --- a/forge/src/main/java/com/mineagent/NetworkHandler.java +++ b/forge/src/main/java/com/mineagent/NetworkHandler.java @@ -10,7 +10,6 @@ import java.nio.channels.SocketChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @@ -18,274 +17,387 @@ import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; +/** + * Handles network communication between the Minecraft mod and Python agent. + * Uses Unix domain sockets for low-latency IPC. + */ public class NetworkHandler implements Runnable { - private static final Logger LOGGER = LogUtils.getLogger(); - private static final String SEND_SOCKET_PATH = "/tmp/mineagent_send.sock"; - private static final String RECEIVE_SOCKET_PATH = "/tmp/mineagent_receive.sock"; - private static final ExecutorService senderExecutor = Executors.newCachedThreadPool(); - private static final ExecutorService receiverExecutor = Executors.newCachedThreadPool(); - private Thread sendThread; - private Thread receiveThread; - private ServerSocketChannel sendSocketChannel; - private ServerSocketChannel receiveSocketChannel; - private final AtomicBoolean running = new AtomicBoolean(true); + private static final Logger LOGGER = LogUtils.getLogger(); + + // Socket paths for Unix domain sockets + private static final String OBSERVATION_SOCKET_PATH = "/tmp/mineagent_observation.sock"; + private static final String ACTION_SOCKET_PATH = "/tmp/mineagent_action.sock"; + + // Thread pool for handling clients + private static final ExecutorService observationExecutor = Executors.newCachedThreadPool(); + private static final ExecutorService actionExecutor = Executors.newCachedThreadPool(); + + // Server socket channels + private ServerSocketChannel observationSocketChannel; + private ServerSocketChannel actionSocketChannel; + + // Client handling threads + private Thread observationThread; + private Thread actionThread; + + // Running state + private final AtomicBoolean running = new AtomicBoolean(true); - // Async observation sending - // TODO: Move this AtomicReference to DataBridge - private final AtomicReference latestObservation = new AtomicReference(); - private final Semaphore frameAvailable = new Semaphore(0); + // Async observation sending + private final AtomicReference latestObservation = new AtomicReference<>(); + private final Semaphore frameAvailable = new Semaphore(0); - // Observation data container - // TODO: Move to custom class that has serialization methods - private static class Observation { - final byte[] frameBuffer; - final double reward; - final long timestamp; + // Internal observation data container + private static class ObservationData { + final byte[] frameBuffer; + final double reward; + final long timestamp; - Observation(byte[] frameBuffer, double reward) { - this.frameBuffer = frameBuffer; - this.reward = reward; - this.timestamp = System.currentTimeMillis(); + ObservationData(byte[] frameBuffer, double reward) { + this.frameBuffer = frameBuffer; + this.reward = reward; + this.timestamp = System.currentTimeMillis(); + } } - } - @Override - public void run() { - try { - Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); - Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); - - observationSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - observationSocketChannel.bind(UnixDomainSocketAddress.of(OBSERVATION_SOCKET_PATH)); - observationSocketChannel.configureBlocking(true); + @Override + public void run() { + try { + // Clean up any existing socket files + Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); + Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); - actionSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - actionSocketChannel.bind(UnixDomainSocketAddress.of(ACTION_SOCKET_PATH)); - actionSocketChannel.configureBlocking(true); + // Create observation socket + observationSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + observationSocketChannel.bind(UnixDomainSocketAddress.of(OBSERVATION_SOCKET_PATH)); + observationSocketChannel.configureBlocking(true); - LOGGER.info("Socket files created: {} and {}", OBSERVATION_SOCKET_PATH, ACTION_SOCKET_PATH); + // Create action socket + actionSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + actionSocketChannel.bind(UnixDomainSocketAddress.of(ACTION_SOCKET_PATH)); + actionSocketChannel.configureBlocking(true); - // Verify socket files were actually created - if (!Files.exists(Path.of(OBSERVATION_SOCKET_PATH))) { - throw new IOException("Failed to create observation socket file: " + OBSERVATION_SOCKET_PATH); - } - if (!Files.exists(Path.of(ACTION_SOCKET_PATH))) { - throw new IOException("Failed to create action socket file: " + ACTION_SOCKET_PATH); - } + LOGGER.info("Socket files created: {} and {}", OBSERVATION_SOCKET_PATH, ACTION_SOCKET_PATH); - LOGGER.info( - "Socket files verified - Observation: {}, Action: {}", - Files.exists(Path.of(OBSERVATION_SOCKET_PATH)), - Files.exists(Path.of(ACTION_SOCKET_PATH))); + // Verify socket files were created + if (!Files.exists(Path.of(OBSERVATION_SOCKET_PATH))) { + throw new IOException("Failed to create observation socket file: " + OBSERVATION_SOCKET_PATH); + } + if (!Files.exists(Path.of(ACTION_SOCKET_PATH))) { + throw new IOException("Failed to create action socket file: " + ACTION_SOCKET_PATH); + } - observationThread = new Thread(this::acceptObservationClients, "ObservationClients"); - actionThread = new Thread(this::acceptActionClients, "ActionClients"); + LOGGER.info("Socket files verified - Observation: {}, Action: {}", + Files.exists(Path.of(OBSERVATION_SOCKET_PATH)), + Files.exists(Path.of(ACTION_SOCKET_PATH))); - observationThread.start(); - actionThread.start(); + // Start client acceptor threads + observationThread = new Thread(this::acceptObservationClients, "ObservationClients"); + actionThread = new Thread(this::acceptActionClients, "ActionClients"); - // Keep main thread alive while server is running - while (this.running.get() && !Thread.currentThread().isInterrupted()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } + observationThread.start(); + actionThread.start(); - } catch (IOException e) { - LOGGER.error("Error starting network server", e); - } finally { - this.cleanup(); - } - } + // Keep main thread alive while server is running + while (this.running.get() && !Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } - private void acceptObservationClients() { - LOGGER.info("Observation clients acceptor thread started"); - while (this.running.get() && !Thread.currentThread().isInterrupted()) { - try { - SocketChannel clientSocket = observationSocketChannel.accept(); - LOGGER.info("Observation client connected: " + clientSocket.getRemoteAddress()); - // 1MB send buffer which can fit small frames - clientSocket.setOption(StandardSocketOptions.SO_SNDBUF, 1024 * 1024); - handleObservationClient(clientSocket); - } catch (IOException e) { - if (this.running.get()) { - LOGGER.error("Error accepting observation client", e); - } else { - LOGGER.info("Observation socket channel closed, stopping accept loop"); - break; + } catch (IOException e) { + LOGGER.error("Error starting network server", e); + } finally { + this.cleanup(); } - } } - LOGGER.info("Observation clients acceptor thread stopped"); - } - private void acceptActionClients() { - LOGGER.info("Action clients acceptor thread started"); - while (this.running.get() && !Thread.currentThread().isInterrupted()) { - try { - SocketChannel clientSocket = actionSocketChannel.accept(); - LOGGER.info("Action client connected: " + clientSocket.getRemoteAddress()); - handleActionClient(clientSocket); - } catch (IOException e) { - if (this.running.get()) { - LOGGER.error("Error accepting action client", e); - } else { - LOGGER.info("Action socket channel closed, stopping accept loop"); - break; + private void acceptObservationClients() { + LOGGER.info("Observation clients acceptor thread started"); + while (this.running.get() && !Thread.currentThread().isInterrupted()) { + try { + SocketChannel clientSocket = observationSocketChannel.accept(); + LOGGER.info("Observation client connected: {}", clientSocket.getRemoteAddress()); + // 1MB send buffer for frames + clientSocket.setOption(StandardSocketOptions.SO_SNDBUF, 1024 * 1024); + handleObservationClient(clientSocket); + } catch (IOException e) { + if (this.running.get()) { + LOGGER.error("Error accepting observation client", e); + } else { + LOGGER.info("Observation socket channel closed, stopping accept loop"); + break; + } + } } - } + LOGGER.info("Observation clients acceptor thread stopped"); } - LOGGER.info("Action clients acceptor thread stopped"); - } - private void handleActionClient(SocketChannel clientSocket) { - actionExecutor.submit( - () -> { - try { - clientSocket.configureBlocking(true); - ByteBuffer actionBuffer = ByteBuffer.allocate(12); // Action is exactly 12 bytes - - while (this.running.get()) { - actionBuffer.clear(); - - // Read exactly 11 bytes for one Action - int totalBytesRead = 0; - while (totalBytesRead < 12) { - int bytesRead = clientSocket.read(actionBuffer); - if (bytesRead == -1) { - // Client disconnected - LOGGER.info("Client disconnected"); - return; + private void acceptActionClients() { + LOGGER.info("Action clients acceptor thread started"); + while (this.running.get() && !Thread.currentThread().isInterrupted()) { + try { + SocketChannel clientSocket = actionSocketChannel.accept(); + LOGGER.info("Action client connected: {}", clientSocket.getRemoteAddress()); + + // Mark client as connected for input suppression + DataBridge.getInstance().setClientConnected(true); + + handleActionClient(clientSocket); + } catch (IOException e) { + if (this.running.get()) { + LOGGER.error("Error accepting action client", e); + } else { + LOGGER.info("Action socket channel closed, stopping accept loop"); + break; } - totalBytesRead += bytesRead; - } - - // Process each action asynchronously to avoid blocking - CompletableFuture.runAsync(() -> processAction(actionBuffer), actionExecutor) - .exceptionally( - throwable -> { - LOGGER.error("Error processing action: " + actionBuffer, throwable); - return null; - }); } - } catch (IOException e) { - LOGGER.error("Error handling client", e); - } finally { + } + LOGGER.info("Action clients acceptor thread stopped"); + } + + /** + * Handles an action client connection, reading variable-size RawInput messages. + * + * RawInput protocol format: + * - 1 byte: numKeysPressed (0-255) + * - N*2 bytes: keyCodes (shorts) + * - 4 bytes: mouseDeltaX (float) + * - 4 bytes: mouseDeltaY (float) + * - 1 byte: mouseButtons + * - 4 bytes: scrollDelta (float) + * - 2 bytes: textLength + * - M bytes: textBytes (UTF-8) + * + * Minimum size: 16 bytes (no keys, no text) + */ + private void handleActionClient(SocketChannel clientSocket) { + actionExecutor.submit(() -> { try { - clientSocket.close(); + clientSocket.configureBlocking(true); + + // Buffer for reading the header (1 byte for key count) + ByteBuffer headerBuffer = ByteBuffer.allocate(1); + + while (this.running.get()) { + // Step 1: Read the key count (1 byte) + headerBuffer.clear(); + if (readExact(clientSocket, headerBuffer) == -1) { + LOGGER.info("Action client disconnected"); + break; + } + headerBuffer.flip(); + int numKeys = headerBuffer.get() & 0xFF; + + // Step 2: Calculate remaining message size + // keyCodes(N*2) + mouseDx(4) + mouseDy(4) + mouseButtons(1) + scrollDelta(4) + textLen(2) + int fixedSize = (numKeys * 2) + 4 + 4 + 1 + 4 + 2; + ByteBuffer fixedBuffer = ByteBuffer.allocate(fixedSize); + + if (readExact(clientSocket, fixedBuffer) == -1) { + LOGGER.info("Action client disconnected during fixed read"); + break; + } + fixedBuffer.flip(); + + // Read key codes + int[] keyCodes = new int[numKeys]; + for (int i = 0; i < numKeys; i++) { + keyCodes[i] = fixedBuffer.getShort(); + } + + // Read mouse and scroll data + float mouseDx = fixedBuffer.getFloat(); + float mouseDy = fixedBuffer.getFloat(); + byte mouseButtons = fixedBuffer.get(); + float scrollDelta = fixedBuffer.getFloat(); + + // Read text length + int textLength = fixedBuffer.getShort() & 0xFFFF; + + // Step 3: Read text if present + String text = ""; + if (textLength > 0) { + ByteBuffer textBuffer = ByteBuffer.allocate(textLength); + if (readExact(clientSocket, textBuffer) == -1) { + LOGGER.info("Action client disconnected during text read"); + break; + } + textBuffer.flip(); + byte[] textBytes = new byte[textLength]; + textBuffer.get(textBytes); + text = new String(textBytes, java.nio.charset.StandardCharsets.UTF_8); + } + + // Create and process the RawInput + final RawInput rawInput = new RawInput(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + processRawInput(rawInput); + } } catch (IOException e) { - LOGGER.error("Error closing client socket", e); + if (this.running.get()) { + LOGGER.error("Error handling action client", e); + } + } finally { + // Mark client as disconnected + DataBridge.getInstance().setClientConnected(false); + + // Reset input state on disconnect + DataBridge.getInstance().getInputInjector().reset(); + + try { + clientSocket.close(); + } catch (IOException e) { + LOGGER.error("Error closing action client socket", e); + } } - } }); - } + } - private void handleObservationClient(SocketChannel clientSocket) { - observationExecutor.submit( - () -> { - try { - while (this.running.get()) { - try { - frameAvailable.acquire(); - Observation observation = latestObservation.get(); - if (observation != null) { - sendObservationImmediate(observation, clientSocket); - } - } catch (InterruptedException e) { - LOGGER.error("Interrupted while waiting for frame", e); - this.running.set(false); - } + /** + * Reads exactly the buffer's remaining capacity from the socket. + * Returns -1 if the client disconnects, otherwise returns bytes read. + */ + private int readExact(SocketChannel channel, ByteBuffer buffer) throws IOException { + int totalRead = 0; + while (buffer.hasRemaining()) { + int bytesRead = channel.read(buffer); + if (bytesRead == -1) { + return -1; } - } finally { + totalRead += bytesRead; + } + return totalRead; + } + + /** + * Processes a received RawInput by passing it to the DataBridge. + */ + private void processRawInput(RawInput rawInput) { + DataBridge.getInstance().setLatestRawInput(rawInput); + LOGGER.debug("RawInput received: {} keys, mouse=({}, {}), buttons={}, scroll={}, text='{}'", + rawInput.keyCodes().length, + rawInput.mouseDx(), rawInput.mouseDy(), + rawInput.mouseButtons(), + rawInput.scrollDelta(), + rawInput.text()); + } + + private void handleObservationClient(SocketChannel clientSocket) { + observationExecutor.submit(() -> { try { - clientSocket.close(); - } catch (IOException e) { - LOGGER.error("Error closing client socket", e); + while (this.running.get()) { + try { + frameAvailable.acquire(); + ObservationData observation = latestObservation.get(); + if (observation != null) { + sendObservationImmediate(observation, clientSocket); + } + } catch (InterruptedException e) { + LOGGER.info("Observation thread interrupted"); + Thread.currentThread().interrupt(); + break; + } + } + } finally { + try { + clientSocket.close(); + } catch (IOException e) { + LOGGER.error("Error closing observation client socket", e); + } } - } }); - } + } - public void setLatest(byte[] frameBuffer, double reward) { - Observation observation = new Observation(frameBuffer, reward); - latestObservation.set(observation); - frameAvailable.drainPermits(); - frameAvailable.release(); - } + /** + * Sets the latest observation data to be sent to connected clients. + */ + public void setLatest(byte[] frameBuffer, double reward) { + ObservationData observation = new ObservationData(frameBuffer, reward); + latestObservation.set(observation); + frameAvailable.drainPermits(); + frameAvailable.release(); + } - private void sendObservationImmediate(Observation observation, SocketChannel clientSocket) { - try { - int totalSize = 8 + observation.frameBuffer.length; - ByteBuffer buffer = ByteBuffer.allocate(totalSize); - buffer.putDouble(observation.reward); - buffer.putInt(observation.frameBuffer.length); - buffer.put(observation.frameBuffer); - buffer.flip(); - while (buffer.hasRemaining()) { - int bytesWritten = clientSocket.write(buffer); - LOGGER.info("Observation sent: {} bytes", bytesWritten); - } - } catch (IOException e) { - LOGGER.error("Error sending observation", e); + private void sendObservationImmediate(ObservationData observation, SocketChannel clientSocket) { + try { + // Format: reward(8) + frameLength(4) + frame(N) + int totalSize = 8 + 4 + observation.frameBuffer.length; + ByteBuffer buffer = ByteBuffer.allocate(totalSize); + buffer.putDouble(observation.reward); + buffer.putInt(observation.frameBuffer.length); + buffer.put(observation.frameBuffer); + buffer.flip(); + + while (buffer.hasRemaining()) { + clientSocket.write(buffer); + } + LOGGER.debug("Observation sent: {} bytes", totalSize); + } catch (IOException e) { + LOGGER.error("Error sending observation", e); + } } - } - private void processAction(ByteBuffer actionBuffer) { - final Action action = Action.fromBytes(actionBuffer.array()); - DataBridge.getInstance().setLatestAction(action); - LOGGER.info("Action received: {}", action); - } + private void cleanup() { + LOGGER.info("Shutting down NetworkHandler..."); + this.running.set(false); + + // Reset input state on cleanup + DataBridge.getInstance().getInputInjector().reset(); + DataBridge.getInstance().setClientConnected(false); + + // Wait for threads to finish + if (this.observationThread != null) { + try { + this.observationThread.interrupt(); + this.observationThread.join(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + if (this.actionThread != null) { + try { + this.actionThread.interrupt(); + this.actionThread.join(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } - private void cleanup() { - LOGGER.info("Shutting down NetworkHandler..."); - this.running.set(false); - - // Release all pressed keys on cleanup - ClientEventHandler.releaseAllKeys(); - // Wait for threads to finish - if (this.observationThread != null) { - try { - this.observationThread.interrupt(); - this.observationThread.join(5000); // Wait up to 5 seconds - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } + // Shutdown executors + observationExecutor.shutdown(); + actionExecutor.shutdown(); - if (this.actionThread != null) { - try { - this.actionThread.interrupt(); - this.actionThread.join(5000); // Wait up to 5 seconds - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } + // Close socket channels + try { + if (this.observationSocketChannel != null) { + this.observationSocketChannel.close(); + } + if (this.actionSocketChannel != null) { + this.actionSocketChannel.close(); + } + } catch (IOException e) { + LOGGER.error("Error closing socket channels: {}", e.getMessage()); + } - // Shutdown executors - observationExecutor.shutdown(); - actionExecutor.shutdown(); + // Delete socket files + try { + Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); + Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); + } catch (IOException e) { + LOGGER.error("Error deleting socket files: {}", e.getMessage()); + } - try { - if (this.observationSocketChannel != null) { - this.observationSocketChannel.close(); - } - if (this.actionSocketChannel != null) { - this.actionSocketChannel.close(); - } - } catch (IOException e) { - LOGGER.error("Cleanup error: " + e.getMessage()); + LOGGER.info("NetworkHandler shutdown complete"); } - - try { - Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); - Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); - } catch (IOException e) { - LOGGER.error("Cleanup error: " + e.getMessage()); + + /** + * Stops the network handler gracefully. + */ + public void stop() { + this.running.set(false); } - - LOGGER.info("NetworkHandler shutdown complete"); - } } diff --git a/forge/src/main/java/com/mineagent/Observation.java b/forge/src/main/java/com/mineagent/Observation.java index 9c91814..56629cd 100644 --- a/forge/src/main/java/com/mineagent/Observation.java +++ b/forge/src/main/java/com/mineagent/Observation.java @@ -1,8 +1,7 @@ -package com.mvi.mvimod; +package com.mineagent; public record Observation( double reward, - ActionState actionState, byte[] frame ) { public byte[] serialize() { diff --git a/forge/src/main/resources/META-INF/accesstransformer.cfg b/forge/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..2faa013 --- /dev/null +++ b/forge/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,12 @@ +# Access Transformers for MineAgent Mod +# Makes MouseHandler GLFW callback methods accessible for input injection +# Using SRG names as required by Forge + +# MouseHandler.onMove(long, double, double) - mouse position callback +public net.minecraft.client.MouseHandler m_91526_(JDD)V + +# MouseHandler.onPress(long, int, int, int) - mouse button callback +public net.minecraft.client.MouseHandler m_91530_(JIII)V + +# MouseHandler.onScroll(long, double, double) - scroll wheel callback +public net.minecraft.client.MouseHandler m_91561_(JDD)V diff --git a/test_action_client.py b/test_action_client.py index 87e139d..2d9dc0f 100644 --- a/test_action_client.py +++ b/test_action_client.py @@ -1,127 +1,285 @@ #!/usr/bin/env python3 """ -Simple test client for debugging action communication with the Minecraft Forge mod. +Test client for debugging raw input communication with the MineAgent Minecraft mod. -This client connects to the Unix domain sockets created by the MVI Minecraft mod -and allows you to send test actions and receive observations for debugging. +This client connects to the Unix domain sockets created by the MineAgent mod +and allows you to send raw input (GLFW key codes, mouse, scroll) for testing. """ import socket import struct import time import threading -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Optional import argparse -import sys + + +# ============================================================================= +# GLFW Key Code Constants +# Reference: https://www.glfw.org/docs/3.3/group__keys.html +# ============================================================================= + + +class GLFW: + """GLFW key and mouse button constants.""" + + # Printable keys + KEY_SPACE = 32 + KEY_APOSTROPHE = 39 + KEY_COMMA = 44 + KEY_MINUS = 45 + KEY_PERIOD = 46 + KEY_SLASH = 47 + KEY_0 = 48 + KEY_1 = 49 + KEY_2 = 50 + KEY_3 = 51 + KEY_4 = 52 + KEY_5 = 53 + KEY_6 = 54 + KEY_7 = 55 + KEY_8 = 56 + KEY_9 = 57 + KEY_SEMICOLON = 59 + KEY_EQUAL = 61 + KEY_A = 65 + KEY_B = 66 + KEY_C = 67 + KEY_D = 68 + KEY_E = 69 + KEY_F = 70 + KEY_G = 71 + KEY_H = 72 + KEY_I = 73 + KEY_J = 74 + KEY_K = 75 + KEY_L = 76 + KEY_M = 77 + KEY_N = 78 + KEY_O = 79 + KEY_P = 80 + KEY_Q = 81 + KEY_R = 82 + KEY_S = 83 + KEY_T = 84 + KEY_U = 85 + KEY_V = 86 + KEY_W = 87 + KEY_X = 88 + KEY_Y = 89 + KEY_Z = 90 + KEY_LEFT_BRACKET = 91 + KEY_BACKSLASH = 92 + KEY_RIGHT_BRACKET = 93 + KEY_GRAVE_ACCENT = 96 + + # Function keys + KEY_ESCAPE = 256 + KEY_ENTER = 257 + KEY_TAB = 258 + KEY_BACKSPACE = 259 + KEY_INSERT = 260 + KEY_DELETE = 261 + KEY_RIGHT = 262 + KEY_LEFT = 263 + KEY_DOWN = 264 + KEY_UP = 265 + KEY_PAGE_UP = 266 + KEY_PAGE_DOWN = 267 + KEY_HOME = 268 + KEY_END = 269 + KEY_CAPS_LOCK = 280 + KEY_SCROLL_LOCK = 281 + KEY_NUM_LOCK = 282 + KEY_PRINT_SCREEN = 283 + KEY_PAUSE = 284 + KEY_F1 = 290 + KEY_F2 = 291 + KEY_F3 = 292 + KEY_F4 = 293 + KEY_F5 = 294 + KEY_F6 = 295 + KEY_F7 = 296 + KEY_F8 = 297 + KEY_F9 = 298 + KEY_F10 = 299 + KEY_F11 = 300 + KEY_F12 = 301 + + # Modifier keys + KEY_LEFT_SHIFT = 340 + KEY_LEFT_CONTROL = 341 + KEY_LEFT_ALT = 342 + KEY_LEFT_SUPER = 343 + KEY_RIGHT_SHIFT = 344 + KEY_RIGHT_CONTROL = 345 + KEY_RIGHT_ALT = 346 + KEY_RIGHT_SUPER = 347 + KEY_MENU = 348 + + # Mouse buttons + MOUSE_BUTTON_LEFT = 0 + MOUSE_BUTTON_RIGHT = 1 + MOUSE_BUTTON_MIDDLE = 2 + + +# Command name to GLFW key code mapping +COMMAND_TO_KEY = { + # Movement (Minecraft default bindings) + "w": GLFW.KEY_W, + "forward": GLFW.KEY_W, + "s": GLFW.KEY_S, + "back": GLFW.KEY_S, + "a": GLFW.KEY_A, + "left": GLFW.KEY_A, + "d": GLFW.KEY_D, + "right": GLFW.KEY_D, + "space": GLFW.KEY_SPACE, + "jump": GLFW.KEY_SPACE, + "shift": GLFW.KEY_LEFT_SHIFT, + "sneak": GLFW.KEY_LEFT_SHIFT, + "ctrl": GLFW.KEY_LEFT_CONTROL, + "sprint": GLFW.KEY_LEFT_CONTROL, + # Interaction + "e": GLFW.KEY_E, + "inventory": GLFW.KEY_E, + "q": GLFW.KEY_Q, + "drop": GLFW.KEY_Q, + "f": GLFW.KEY_F, + "swap": GLFW.KEY_F, + # Hotbar (number keys) + "1": GLFW.KEY_1, + "hotbar1": GLFW.KEY_1, + "2": GLFW.KEY_2, + "hotbar2": GLFW.KEY_2, + "3": GLFW.KEY_3, + "hotbar3": GLFW.KEY_3, + "4": GLFW.KEY_4, + "hotbar4": GLFW.KEY_4, + "5": GLFW.KEY_5, + "hotbar5": GLFW.KEY_5, + "6": GLFW.KEY_6, + "hotbar6": GLFW.KEY_6, + "7": GLFW.KEY_7, + "hotbar7": GLFW.KEY_7, + "8": GLFW.KEY_8, + "hotbar8": GLFW.KEY_8, + "9": GLFW.KEY_9, + "hotbar9": GLFW.KEY_9, + # Special keys + "esc": GLFW.KEY_ESCAPE, + "escape": GLFW.KEY_ESCAPE, + "enter": GLFW.KEY_ENTER, + "tab": GLFW.KEY_TAB, + "t": GLFW.KEY_T, # Chat + "chat": GLFW.KEY_T, + "/": GLFW.KEY_SLASH, # Command + "command": GLFW.KEY_SLASH, +} + + +# ============================================================================= +# RawInput Data Class +# ============================================================================= @dataclass -class ActionState: - """Represents the state of all persistent actions (keys that can be held down)""" - up: bool = False - down: bool = False - left: bool = False - right: bool = False - jump: bool = False - sneak: bool = False - sprint: bool = False - inventory: bool = False - drop: bool = False - swap: bool = False - use: bool = False - attack: bool = False - pick_item: bool = False - hotbar1: bool = False - hotbar2: bool = False - hotbar3: bool = False - hotbar4: bool = False - hotbar5: bool = False - hotbar6: bool = False - hotbar7: bool = False - hotbar8: bool = False - right_mouse_down: bool = False - left_mouse_down: bool = False +class RawInput: + """ + Raw input data to send to Minecraft. + + Protocol format (variable size): + - 1 byte: numKeysPressed (0-255) + - N*2 bytes: keyCodes (shorts, big-endian) + - 4 bytes: mouseDeltaX (float, big-endian) + - 4 bytes: mouseDeltaY (float, big-endian) + - 1 byte: mouseButtons (bits: 0=left, 1=right, 2=middle) + - 4 bytes: scrollDelta (float, big-endian) + - 2 bytes: textLength (big-endian) + - M bytes: textBytes (UTF-8) + """ + + key_codes: list[int] = field(default_factory=list) + mouse_dx: float = 0.0 + mouse_dy: float = 0.0 + mouse_buttons: int = 0 # Bit flags: 0=left, 1=right, 2=middle + scroll_delta: float = 0.0 + text: str = "" def to_bytes(self) -> bytes: - """Convert action state to 3-byte packed format""" - # First byte: up, down, left, right, jump, sneak, sprint, inventory - first_byte = 0 - first_byte |= (self.up << 7) - first_byte |= (self.down << 6) - first_byte |= (self.left << 5) - first_byte |= (self.right << 4) - first_byte |= (self.jump << 3) - first_byte |= (self.sneak << 2) - first_byte |= (self.sprint << 1) - first_byte |= self.inventory - - # Second byte: drop, swap, use, attack, pick_item, hotbar1, hotbar2, hotbar3 - second_byte = 0 - second_byte |= (self.drop << 7) - second_byte |= (self.swap << 6) - second_byte |= (self.use << 5) - second_byte |= (self.attack << 4) - second_byte |= (self.pick_item << 3) - second_byte |= (self.hotbar1 << 2) - second_byte |= (self.hotbar2 << 1) - second_byte |= self.hotbar3 - - # Third byte: hotbar4, hotbar5, hotbar6, hotbar7, hotbar8, (3 bits padding) - third_byte = 0 - third_byte |= (self.hotbar4 << 7) - third_byte |= (self.hotbar5 << 6) - third_byte |= (self.hotbar6 << 5) - third_byte |= (self.hotbar7 << 4) - third_byte |= (self.hotbar8 << 3) - third_byte |= (self.right_mouse_down << 2) - third_byte |= (self.left_mouse_down << 1) - # Bits 0-2 are padding - - return bytes([first_byte, second_byte, third_byte]) + """Convert to binary protocol format.""" + data = bytearray() + # Number of keys (1 byte) + num_keys = len(self.key_codes) + if num_keys > 255: + raise ValueError(f"Too many keys pressed: {num_keys} (max 255)") + data.append(num_keys) -@dataclass -class Action: - """Complete action including persistent and non-persistent actions""" - action_state: ActionState - exit_menu: bool = False - mouse_control_x: float = 0.0 - mouse_control_y: float = 0.0 + # Key codes (N * 2 bytes, big-endian shorts) + for key_code in self.key_codes: + data.extend(struct.pack(">h", key_code)) + + # Mouse delta X (4 bytes, big-endian float) + data.extend(struct.pack(">f", self.mouse_dx)) + + # Mouse delta Y (4 bytes, big-endian float) + data.extend(struct.pack(">f", self.mouse_dy)) + + # Mouse buttons (1 byte) + data.append(self.mouse_buttons & 0xFF) + + # Scroll delta (4 bytes, big-endian float) + data.extend(struct.pack(">f", self.scroll_delta)) + + # Text length and content + text_bytes = self.text.encode("utf-8") + text_length = len(text_bytes) + if text_length > 65535: + raise ValueError(f"Text too long: {text_length} bytes (max 65535)") + data.extend(struct.pack(">H", text_length)) + data.extend(text_bytes) - def to_bytes(self) -> bytes: - """Convert action to 12-byte format for network transmission""" - data = bytearray(12) - - # First 3 bytes: ActionState - action_bytes = self.action_state.to_bytes() - data[0:3] = action_bytes - - # Byte 3: exit_menu flag (bit 7, other bits are padding) - menu_byte = (1 << 7) if self.exit_menu else 0 - data[3] = menu_byte - - # Bytes 4-7: mouse_control_x (float) - mouse_x_bytes = struct.pack('>f', self.mouse_control_x) - data[4:8] = mouse_x_bytes - - # Bytes 8-11: mouse_control_y (float) - mouse_y_bytes = struct.pack('>f', self.mouse_control_y) - data[8:12] = mouse_y_bytes - return bytes(data) + def set_left_mouse(self, pressed: bool): + """Set left mouse button state.""" + if pressed: + self.mouse_buttons |= 1 << GLFW.MOUSE_BUTTON_LEFT + else: + self.mouse_buttons &= ~(1 << GLFW.MOUSE_BUTTON_LEFT) + + def set_right_mouse(self, pressed: bool): + """Set right mouse button state.""" + if pressed: + self.mouse_buttons |= 1 << GLFW.MOUSE_BUTTON_RIGHT + else: + self.mouse_buttons &= ~(1 << GLFW.MOUSE_BUTTON_RIGHT) + + def set_middle_mouse(self, pressed: bool): + """Set middle mouse button state.""" + if pressed: + self.mouse_buttons |= 1 << GLFW.MOUSE_BUTTON_MIDDLE + else: + self.mouse_buttons &= ~(1 << GLFW.MOUSE_BUTTON_MIDDLE) + -class ActionTestClient: - """Test client for sending actions to the Minecraft mod""" - - def __init__(self, action_socket_path: str = "/tmp/mvi_action.sock"): +# ============================================================================= +# Test Clients +# ============================================================================= + + +class RawInputTestClient: + """Test client for sending raw input to the Minecraft mod.""" + + def __init__(self, action_socket_path: str = "/tmp/mineagent_action.sock"): self.action_socket_path = action_socket_path self.action_socket: Optional[socket.socket] = None self.connected = False - + def connect(self) -> bool: - """Connect to the action socket""" + """Connect to the action socket.""" try: self.action_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.action_socket.connect(self.action_socket_path) @@ -131,42 +289,46 @@ def connect(self) -> bool: except Exception as e: print(f"✗ Failed to connect to action socket: {e}") return False - + def disconnect(self): - """Disconnect from the action socket""" + """Disconnect from the action socket.""" if self.action_socket: self.action_socket.close() self.action_socket = None self.connected = False print("✓ Disconnected from action socket") - - def send_action(self, action: Action) -> bool: - """Send an action to the mod""" + + def send_raw_input(self, raw_input: RawInput) -> bool: + """Send raw input to the mod.""" if not self.connected or not self.action_socket: print("✗ Not connected to action socket") return False - + try: - action_bytes = action.to_bytes() - self.action_socket.send(action_bytes) - print(f"✓ Sent action: {len(action_bytes)} bytes") + input_bytes = raw_input.to_bytes() + self.action_socket.send(input_bytes) + print( + f"✓ Sent raw input: {len(input_bytes)} bytes, {len(raw_input.key_codes)} keys" + ) return True except Exception as e: - print(f"✗ Failed to send action: {e}") + print(f"✗ Failed to send raw input: {e}") return False class ObservationTestClient: - """Test client for receiving observations from the Minecraft mod""" - - def __init__(self, observation_socket_path: str = "/tmp/mvi_observation.sock"): + """Test client for receiving observations from the Minecraft mod.""" + + def __init__( + self, observation_socket_path: str = "/tmp/mineagent_observation.sock" + ): self.observation_socket_path = observation_socket_path self.observation_socket: Optional[socket.socket] = None self.connected = False self.running = False - + def connect(self) -> bool: - """Connect to the observation socket""" + """Connect to the observation socket.""" try: self.observation_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.observation_socket.connect(self.observation_socket_path) @@ -176,56 +338,58 @@ def connect(self) -> bool: except Exception as e: print(f"✗ Failed to connect to observation socket: {e}") return False - + def disconnect(self): - """Disconnect from the observation socket""" + """Disconnect from the observation socket.""" self.running = False if self.observation_socket: self.observation_socket.close() self.observation_socket = None self.connected = False print("✓ Disconnected from observation socket") - + def receive_observations(self): - """Continuously receive and print observation info""" + """Continuously receive and print observation info.""" if not self.connected or not self.observation_socket: print("✗ Not connected to observation socket") return - + self.running = True observation_count = 0 - + try: while self.running: # Read reward (8 bytes, double) reward_data = self._read_exact(8) if not reward_data: break - reward = struct.unpack('>d', reward_data)[0] - + reward = struct.unpack(">d", reward_data)[0] + # Read frame length (4 bytes, int) length_data = self._read_exact(4) if not length_data: break - frame_length = struct.unpack('>I', length_data)[0] - + frame_length = struct.unpack(">I", length_data)[0] + # Read frame data frame_data = self._read_exact(frame_length) if not frame_data: break - + observation_count += 1 - print(f"✓ Observation #{observation_count}: reward={reward:.3f}, frame={frame_length} bytes") - + print( + f"✓ Observation #{observation_count}: reward={reward:.3f}, frame={frame_length} bytes" + ) + except Exception as e: print(f"✗ Error receiving observations: {e}") - + def _read_exact(self, n: int) -> Optional[bytes]: - """Read exactly n bytes from the socket""" + """Read exactly n bytes from the socket.""" if not self.observation_socket: return None - - data = b'' + + data = b"" while len(data) < n: try: chunk = self.observation_socket.recv(n - len(data)) @@ -237,344 +401,424 @@ def _read_exact(self, n: int) -> Optional[bytes]: return data +# ============================================================================= +# Command Parsing and Help +# ============================================================================= + + def show_help(): - """Display comprehensive help information""" - print("\n" + "=" * 60) - print("MVI ACTION TEST CLIENT - COMMAND REFERENCE") - print("=" * 60) - print("\nMOVEMENT COMMANDS:") - print(" w, forward - Move forward") - print(" s, back - Move backward") - print(" a, left - Move left") - print(" d, right - Move right") - print(" space, jump - Jump") - print(" shift, sneak - Sneak/crouch") - print(" ctrl, sprint - Sprint") + """Display comprehensive help information.""" + print("\n" + "=" * 70) + print("MINEAGENT RAW INPUT TEST CLIENT - COMMAND REFERENCE") + print("=" * 70) + print("\nMOVEMENT COMMANDS (momentary - pressed then released):") + print(" w, forward - Move forward (GLFW_KEY_W = 87)") + print(" s, back - Move backward (GLFW_KEY_S = 83)") + print(" a, left - Move left (GLFW_KEY_A = 65)") + print(" d, right - Move right (GLFW_KEY_D = 68)") + print(" space, jump - Jump (GLFW_KEY_SPACE = 32)") + print(" shift, sneak - Sneak (GLFW_KEY_LEFT_SHIFT = 340)") + print(" ctrl, sprint - Sprint (GLFW_KEY_LEFT_CONTROL = 341)") print() print("INTERACTION COMMANDS:") - print(" attack, lmb - Attack/left mouse button") - print(" use, rmb - Use/right mouse button") - print(" e, inventory - Open inventory") - print(" q, drop - Drop item") - print(" f, swap - Swap offhand") - print(" pick - Pick block") + print(" e, inventory - Open inventory (GLFW_KEY_E = 69)") + print(" q, drop - Drop item (GLFW_KEY_Q = 81)") + print(" f, swap - Swap offhand (GLFW_KEY_F = 70)") print() print("HOTBAR COMMANDS:") - print(" 1-8 - Select hotbar slots 1-8") - print(" hotbar1-8 - Select hotbar slots (alternative)") + print(" 1-9 - Select hotbar slots (GLFW_KEY_1-9)") print() print("MOUSE COMMANDS:") - print(" mouse - Move mouse by x,y amount") - print(" turn - Turn horizontally by x amount") - print(" look - Look vertically by y amount") - print(" mleft, mright - Quick turn left/right") - print(" mup, mdown - Quick look up/down") - print(" lclick, rclick - Quick left/right mouse click") - print(" mouseleft - Hold or release left mouse (GUI)") - print(" mouseright - Hold or release right mouse (GUI)") + print(" mouse - Move mouse by delta (x=yaw/right, y=pitch/down)") + print(" lclick - Left mouse click (one-shot, for single attack/interact)") + print(" rclick - Right mouse click (one-shot)") + print(" mclick - Middle mouse click (one-shot)") + print(" ldown, lup - Hold/release left mouse (persistent until lup)") + print(" rdown, rup - Hold/release right mouse (persistent until rup)") + print(" scroll - Scroll wheel (positive = up)") print() - print("SPECIAL COMMANDS:") - print(" esc, exit - Exit menu/GUI") - print(" combo - Execute multiple actions together") + print("TEXT INPUT:") + print(" text - Send raw text input (for chat/signs when open)") + print(" say - Open chat, type message, and send it") + print() + print("COMBO/HOLD COMMANDS:") + print(" combo - Multiple keys pressed together (momentary)") + print(" hold - Hold keys continuously (persistent until release)") + print(" release - Release all held keys AND mouse buttons") print() print("UTILITY COMMANDS:") print(" help, h, ? - Show this help") - print(" status - Show connection status") + print(" status - Show connection status and held state") print(" test - Run quick test sequence") print(" clear - Clear screen") - print(" quit, exit, q - Exit program") - print("\nEXAMPLES:") - print(" combo w space - Move forward and jump") - print(" mouse -10 5 - Turn left 10 units, look up 5 units") - print(" turn 45 - Turn right 45 degrees") - print("=" * 60) + print(" quit, q - Exit program") + print() + print("EXAMPLES:") + print(" w - Press W once (brief forward movement)") + print(" hold w - Hold W (continuous forward movement)") + print(" hold w ctrl - Hold W and sprint together") + print(" combo w space - Move forward and jump together (momentary)") + print(" mouse 100 0 - Turn right") + print(" mouse 0 -50 - Look up") + print(" lclick - Left click once (attack)") + print(" ldown - Hold left mouse (for breaking blocks)") + print(" lup - Release left mouse") + print(" scroll 1 - Scroll hotbar up") + print(" say Hello world! - Open chat, type 'Hello world!', and send") + print(" release - Release everything") + print("=" * 70) + + +class HeldState: + """Tracks held keys and mouse buttons across commands.""" + + def __init__(self): + self.keys: set[int] = set() + self.mouse_buttons: int = 0 # Bit flags: 0=left, 1=right, 2=middle + + def set_mouse_button(self, button: int, pressed: bool): + """Set a mouse button state. button: 0=left, 1=right, 2=middle""" + if pressed: + self.mouse_buttons |= 1 << button + else: + self.mouse_buttons &= ~(1 << button) + def clear(self): + """Clear all held state.""" + self.keys.clear() + self.mouse_buttons = 0 -def parse_command(command_line: str) -> tuple[ActionState, bool, float, float]: + +def parse_command( + command_line: str, held_keys: set[int], held_state: Optional["HeldState"] = None +) -> Optional[RawInput]: """ - Parse command line input and return action components - Returns: (action_state, exit_menu, mouse_x, mouse_y) + Parse command line input and return a RawInput. + + Args: + command_line: The command string to parse + held_keys: Set of currently held key codes (modified in place) - DEPRECATED, use held_state + held_state: HeldState object tracking keys and mouse buttons + + Returns: + RawInput to send, or None if command was informational only """ + # Use held_state if provided, otherwise fall back to old behavior + if held_state is None: + held_state = HeldState() + held_state.keys = held_keys + parts = command_line.strip().lower().split() if not parts: - return ActionState(), False, 0.0, 0.0 - + return None + main_command = parts[0] - action_state = ActionState() - exit_menu = False - mouse_x, mouse_y = 0.0, 0.0 - - # Handle special commands first + + # Handle special commands if main_command in ["help", "h", "?"]: show_help() - return ActionState(), False, 0.0, 0.0 - - if main_command == "combo" and len(parts) > 1: - # Execute multiple actions together - for action_name in parts[1:]: - _apply_action_to_state(action_state, action_name) - return action_state, exit_menu, mouse_x, mouse_y - + return None + + raw_input = RawInput() + + # Mouse movement if main_command == "mouse" and len(parts) >= 3: try: - mouse_x = float(parts[1]) - mouse_y = float(parts[2]) + raw_input.mouse_dx = float(parts[1]) + raw_input.mouse_dy = float(parts[2]) + raw_input.key_codes = list(held_state.keys) + raw_input.mouse_buttons = held_state.mouse_buttons + return raw_input except ValueError: print("✗ Invalid mouse coordinates. Use: mouse ") - return action_state, exit_menu, mouse_x, mouse_y - - if main_command == "turn" and len(parts) >= 2: - try: - mouse_x = float(parts[1]) - except ValueError: - print("✗ Invalid turn amount. Use: turn ") - return action_state, exit_menu, mouse_x, mouse_y - - if main_command == "look" and len(parts) >= 2: + return None + + # Scroll wheel + if main_command == "scroll" and len(parts) >= 2: try: - mouse_y = float(parts[1]) + raw_input.scroll_delta = float(parts[1]) + raw_input.key_codes = list(held_state.keys) + raw_input.mouse_buttons = held_state.mouse_buttons + return raw_input except ValueError: - print("✗ Invalid look amount. Use: look ") - return action_state, exit_menu, mouse_x, mouse_y - - # Mouse button control (GUI) - explicit down/up - if main_command in ["mouseleft", "mouseright"]: - state = None - if len(parts) >= 2: - if parts[1] in ["down", "press", "on"]: - state = True - elif parts[1] in ["up", "release", "off"]: - state = False - # Default to down if not specified - if state is None: - state = True - if main_command == "mouseleft": - action_state.left_mouse_down = state - else: - action_state.right_mouse_down = state - return action_state, exit_menu, mouse_x, mouse_y - - # Handle regular single actions - _apply_action_to_state(action_state, main_command) - - # Handle special cases - if main_command in ["esc", "exit"]: - exit_menu = True - elif main_command == "mleft": - mouse_x = -10.0 - elif main_command == "mright": - mouse_x = 10.0 - elif main_command == "mup": - mouse_y = -10.0 - elif main_command == "mdown": - mouse_y = 10.0 - - return action_state, exit_menu, mouse_x, mouse_y - - -def _apply_action_to_state(action_state: ActionState, action_name: str): - """Apply a single action to the action state""" - action_name = action_name.lower() - - # Movement - if action_name in ["w", "forward"]: - action_state.up = True - elif action_name in ["s", "back"]: - action_state.down = True - elif action_name in ["a", "left"]: - action_state.left = True - elif action_name in ["d", "right"]: - action_state.right = True - elif action_name in ["space", "jump"]: - action_state.jump = True - elif action_name in ["shift", "sneak"]: - action_state.sneak = True - elif action_name in ["ctrl", "sprint"]: - action_state.sprint = True - - # Interactions - elif action_name in ["attack", "lmb"]: - action_state.attack = True - elif action_name in ["use", "rmb"]: - action_state.use = True - elif action_name in ["e", "inventory"]: - action_state.inventory = True - elif action_name in ["q", "drop"]: - action_state.drop = True - elif action_name in ["f", "swap"]: - action_state.swap = True - elif action_name == "pick": - action_state.pick_item = True - - # Hotbar - elif action_name in ["1", "hotbar1"]: - action_state.hotbar1 = True - elif action_name in ["2", "hotbar2"]: - action_state.hotbar2 = True - elif action_name in ["3", "hotbar3"]: - action_state.hotbar3 = True - elif action_name in ["4", "hotbar4"]: - action_state.hotbar4 = True - elif action_name in ["5", "hotbar5"]: - action_state.hotbar5 = True - elif action_name in ["6", "hotbar6"]: - action_state.hotbar6 = True - elif action_name in ["7", "hotbar7"]: - action_state.hotbar7 = True - elif action_name in ["8", "hotbar8"]: - action_state.hotbar8 = True + print("✗ Invalid scroll amount. Use: scroll ") + return None + + # Text input + if main_command == "text" and len(parts) >= 2: + raw_input.text = " ".join(parts[1:]) + raw_input.key_codes = list(held_state.keys) + raw_input.mouse_buttons = held_state.mouse_buttons + return raw_input + + # Chat shortcut - returns a list of RawInputs to send in sequence + if main_command == "say" and len(parts) >= 2: + message = " ".join(parts[1:]) + # Return special marker - will be handled by caller + raw_input.key_codes = [GLFW.KEY_T] + raw_input.text = f"__SAY__{message}" # Special marker for say command + return raw_input + + # Mouse clicks (single shot - press then immediately release) + # These are one-shot and don't modify held state + if main_command == "lclick": + raw_input.mouse_buttons = held_state.mouse_buttons | ( + 1 << GLFW.MOUSE_BUTTON_LEFT + ) + raw_input.key_codes = list(held_state.keys) + return raw_input + if main_command == "rclick": + raw_input.mouse_buttons = held_state.mouse_buttons | ( + 1 << GLFW.MOUSE_BUTTON_RIGHT + ) + raw_input.key_codes = list(held_state.keys) + return raw_input + if main_command == "mclick": + raw_input.mouse_buttons = held_state.mouse_buttons | ( + 1 << GLFW.MOUSE_BUTTON_MIDDLE + ) + raw_input.key_codes = list(held_state.keys) + return raw_input + + # Mouse hold/release - these modify the held state + if main_command == "ldown": + held_state.set_mouse_button(GLFW.MOUSE_BUTTON_LEFT, True) + raw_input.mouse_buttons = held_state.mouse_buttons + raw_input.key_codes = list(held_state.keys) + return raw_input + if main_command == "lup": + held_state.set_mouse_button(GLFW.MOUSE_BUTTON_LEFT, False) + raw_input.mouse_buttons = held_state.mouse_buttons + raw_input.key_codes = list(held_state.keys) + return raw_input + if main_command == "rdown": + held_state.set_mouse_button(GLFW.MOUSE_BUTTON_RIGHT, True) + raw_input.mouse_buttons = held_state.mouse_buttons + raw_input.key_codes = list(held_state.keys) + return raw_input + if main_command == "rup": + held_state.set_mouse_button(GLFW.MOUSE_BUTTON_RIGHT, False) + raw_input.mouse_buttons = held_state.mouse_buttons + raw_input.key_codes = list(held_state.keys) + return raw_input + + # Release all keys and mouse buttons + if main_command == "release": + held_keys.clear() + held_state.clear() + return raw_input # Empty state releases all + + # Combo command (press multiple keys together, then release) + if main_command == "combo" and len(parts) > 1: + for action_name in parts[1:]: + if action_name in COMMAND_TO_KEY: + raw_input.key_codes.append(COMMAND_TO_KEY[action_name]) + raw_input.mouse_buttons = held_state.mouse_buttons + return raw_input + + # Hold command (add to held keys) + if main_command == "hold" and len(parts) > 1: + for action_name in parts[1:]: + if action_name in COMMAND_TO_KEY: + held_state.keys.add(COMMAND_TO_KEY[action_name]) + held_keys.add(COMMAND_TO_KEY[action_name]) # Keep old behavior too + raw_input.key_codes = list(held_state.keys) + raw_input.mouse_buttons = held_state.mouse_buttons + return raw_input + + # Single key command (momentary press - does NOT add to held state) + if main_command in COMMAND_TO_KEY: + raw_input.key_codes = [COMMAND_TO_KEY[main_command]] + raw_input.mouse_buttons = held_state.mouse_buttons + return raw_input + + # Try to interpret as raw key code + try: + key_code = int(main_command) + raw_input.key_codes = [key_code] + raw_input.mouse_buttons = held_state.mouse_buttons + return raw_input + except ValueError: + pass + + print(f"✗ Unknown command: {main_command}") + return None + + +# ============================================================================= +# Main Functions +# ============================================================================= def run_interactive_mode(): - """Run enhanced interactive mode for testing actions""" - action_client = ActionTestClient() + """Run interactive mode for testing raw input.""" + client = RawInputTestClient() observation_client = ObservationTestClient() - - print("MVI Action Test Client - Interactive Mode") + held_state = HeldState() + held_keys = held_state.keys # For backward compatibility + + print("MineAgent Raw Input Test Client - Interactive Mode") print("=" * 50) - + # Connect to action socket - if not action_client.connect(): + if not client.connect(): return - + # Optionally connect to observation socket print("\nDo you want to monitor observations? (y/n): ", end="") - if input().lower().startswith('y'): - if observation_client.connect(): - obs_thread = threading.Thread(target=observation_client.receive_observations, daemon=True) - obs_thread.start() - - print("\nInteractive Action Testing Started") + try: + if input().lower().startswith("y"): + if observation_client.connect(): + obs_thread = threading.Thread( + target=observation_client.receive_observations, daemon=True + ) + obs_thread.start() + except EOFError: + pass + + print("\nInteractive Raw Input Testing Started") print("Type 'help' for command reference, 'quit' to exit") print("-" * 50) - + try: while True: try: - command_line = input("Action> ").strip() - + command_line = input("RawInput> ").strip() + if not command_line: continue - + # Handle special meta commands - if command_line.lower() in ["quit", "exit", "q"]: + if command_line.lower() in ["quit", "q"]: break elif command_line.lower() == "status": - print(f"✓ Action socket: {'Connected' if action_client.connected else 'Disconnected'}") - print(f"✓ Observation socket: {'Connected' if observation_client.connected else 'Disconnected'}") + print( + f"✓ Action socket: {'Connected' if client.connected else 'Disconnected'}" + ) + print( + f"✓ Observation socket: {'Connected' if observation_client.connected else 'Disconnected'}" + ) + print(f"✓ Held keys: {held_state.keys}") + print( + f"✓ Held mouse buttons: {held_state.mouse_buttons} (L={bool(held_state.mouse_buttons & 1)}, R={bool(held_state.mouse_buttons & 2)}, M={bool(held_state.mouse_buttons & 4)})" + ) continue elif command_line.lower() == "clear": print("\033[2J\033[H") # Clear screen continue - elif command_line.lower() == "test": - print("Running quick test sequence...") - test_sequence = ["w", "a", "s", "d", "space", "attack"] - for test_cmd in test_sequence: - print(f" Testing: {test_cmd}") - action_state, exit_menu, mouse_x, mouse_y = parse_command(test_cmd) - action = Action(action_state, exit_menu, mouse_x, mouse_y) - action_client.send_action(action) - time.sleep(0.3) - print("✓ Test sequence completed") + run_test_sequence(client) continue - # Quick mouse click helpers - if command_line.lower() in ["lclick", "rclick"]: - is_left = command_line.lower() == "lclick" - # Press - press_state = ActionState(left_mouse_down=True) if is_left else ActionState(right_mouse_down=True) - action = Action(press_state, False, 0.0, 0.0) - action_client.send_action(action) - time.sleep(0.05) - # Release - release_state = ActionState(left_mouse_down=False, right_mouse_down=False) - action = Action(release_state, False, 0.0, 0.0) - action_client.send_action(action) - continue - - # Parse the command - action_state, exit_menu, mouse_x, mouse_y = parse_command(command_line) - - # Skip if it was just a help command - if command_line.lower() in ["help", "h", "?"]: - continue - - # Send the action - action = Action(action_state, exit_menu, mouse_x, mouse_y) - action_client.send_action(action) - + # Parse and send command + raw_input = parse_command(command_line, held_keys, held_state) + if raw_input is not None: + # Handle special 'say' command (multi-step) + if raw_input.text.startswith("__SAY__"): + message = raw_input.text[7:] # Remove __SAY__ prefix + print(f" Opening chat and typing: {message}") + + # Step 1: Press T to open chat + client.send_raw_input(RawInput(key_codes=[GLFW.KEY_T])) + time.sleep(0.15) # Wait for chat to open + + # Step 2: Release T + client.send_raw_input(RawInput()) + time.sleep(0.05) + + # Step 3: Type the message + client.send_raw_input(RawInput(text=message)) + time.sleep(0.05) + + # Step 4: Press Enter to send + client.send_raw_input(RawInput(key_codes=[GLFW.KEY_ENTER])) + time.sleep(0.05) + + # Step 5: Release Enter + client.send_raw_input(RawInput()) + else: + client.send_raw_input(raw_input) + except EOFError: print("\nEOF received, exiting...") break except Exception as e: print(f"✗ Error processing command: {e}") - + except KeyboardInterrupt: print("\nInterrupted by user") finally: - action_client.disconnect() + # Send release all before disconnect + client.send_raw_input(RawInput()) + client.disconnect() observation_client.disconnect() +def run_test_sequence(client: RawInputTestClient): + """Run a quick test sequence.""" + print("Running test sequence...") + + tests = [ + ("Move Forward (W)", RawInput(key_codes=[GLFW.KEY_W])), + ("Move Left (A)", RawInput(key_codes=[GLFW.KEY_A])), + ("Move Back (S)", RawInput(key_codes=[GLFW.KEY_S])), + ("Move Right (D)", RawInput(key_codes=[GLFW.KEY_D])), + ("Jump (Space)", RawInput(key_codes=[GLFW.KEY_SPACE])), + ("Turn Right", RawInput(mouse_dx=50.0)), + ("Turn Left", RawInput(mouse_dx=-50.0)), + ("Look Up", RawInput(mouse_dy=-30.0)), + ("Look Down", RawInput(mouse_dy=30.0)), + ("Left Click", RawInput(mouse_buttons=1)), + ("Right Click", RawInput(mouse_buttons=2)), + ("Scroll Up", RawInput(scroll_delta=1.0)), + ("Release All", RawInput()), + ] + + for name, raw_input in tests: + print(f" Testing: {name}") + client.send_raw_input(raw_input) + time.sleep(0.3) + + print("✓ Test sequence completed") + + def run_automated_test(): - """Run automated test sequence""" - action_client = ActionTestClient() - - print("MVI Automated Action Test") + """Run automated test sequence.""" + client = RawInputTestClient() + + print("MineAgent Automated Raw Input Test") print("=" * 50) - - if not action_client.connect(): + + if not client.connect(): return - + try: - test_actions = [ - ("Forward", ActionState(up=True)), - ("Back", ActionState(down=True)), - ("Left", ActionState(left=True)), - ("Right", ActionState(right=True)), - ("Jump", ActionState(jump=True)), - ("Sneak", ActionState(sneak=True)), - ("Sprint", ActionState(sprint=True)), - ("Attack", ActionState(attack=True)), - ("Use", ActionState(use=True)), - ("Hotbar 1", ActionState(hotbar1=True)), - ("Turn Left", ActionState(), False, -10.0, 0.0), - ("Turn Right", ActionState(), False, 10.0, 0.0), - ("Exit Menu", ActionState(), True, 0.0, 0.0), - ] - - print("Running automated test sequence...") - for i, test_data in enumerate(test_actions): - name = test_data[0] - action_state = test_data[1] - exit_menu = test_data[2] if len(test_data) > 2 else False - mouse_x = test_data[3] if len(test_data) > 3 else 0.0 - mouse_y = test_data[4] if len(test_data) > 4 else 0.0 - - print(f" {i+1:2d}. {name}") - action = Action(action_state, exit_menu, mouse_x, mouse_y) - action_client.send_action(action) - time.sleep(0.5) - - print("✓ Automated test completed successfully") - + run_test_sequence(client) except KeyboardInterrupt: print("\nTest interrupted by user") finally: - action_client.disconnect() + client.send_raw_input(RawInput()) # Release all + client.disconnect() def main(): - parser = argparse.ArgumentParser(description="MVI Action Test Client") - parser.add_argument("--auto", action="store_true", help="Run automated test sequence") - parser.add_argument("--action-socket", default="/tmp/mvi_action.sock", - help="Path to action socket (default: /tmp/mvi_action.sock)") - parser.add_argument("--obs-socket", default="/tmp/mvi_observation.sock", - help="Path to observation socket (default: /tmp/mvi_observation.sock)") - + parser = argparse.ArgumentParser(description="MineAgent Raw Input Test Client") + parser.add_argument( + "--auto", action="store_true", help="Run automated test sequence" + ) + parser.add_argument( + "--action-socket", + default="/tmp/mineagent_action.sock", + help="Path to action socket (default: /tmp/mineagent_action.sock)", + ) + parser.add_argument( + "--obs-socket", + default="/tmp/mineagent_observation.sock", + help="Path to observation socket (default: /tmp/mineagent_observation.sock)", + ) + args = parser.parse_args() - + if args.auto: run_automated_test() else: @@ -582,4 +826,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() From 1bcc08d1f88ce13b33d73ca7f9f207e610c18d86 Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 24 Jan 2026 11:08:19 -0500 Subject: [PATCH 14/21] Tests --- .../src/main/java/com/mineagent/RawInput.java | 2 + .../test/java/com/mineagent/RawInputTest.java | 2 + pixi.lock | 1020 +---------------- pyproject.toml | 19 +- 4 files changed, 28 insertions(+), 1015 deletions(-) diff --git a/forge/src/main/java/com/mineagent/RawInput.java b/forge/src/main/java/com/mineagent/RawInput.java index cfc2ede..dc6ca78 100644 --- a/forge/src/main/java/com/mineagent/RawInput.java +++ b/forge/src/main/java/com/mineagent/RawInput.java @@ -1,6 +1,7 @@ package com.mineagent; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; public record RawInput( @@ -13,6 +14,7 @@ public record RawInput( ) { public static RawInput fromBytes(byte[] bytes) { ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.order(ByteOrder.BIG_ENDIAN); // Match Python protocol (struct.pack with '>') // Keys int numKeysPressed = buffer.get() & 0xFF; diff --git a/forge/src/test/java/com/mineagent/RawInputTest.java b/forge/src/test/java/com/mineagent/RawInputTest.java index 1dc2322..fc3f0bd 100644 --- a/forge/src/test/java/com/mineagent/RawInputTest.java +++ b/forge/src/test/java/com/mineagent/RawInputTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; @@ -34,6 +35,7 @@ private byte[] createRawInputBytes( byte[] textBytes = text.getBytes(StandardCharsets.UTF_8); int bufferSize = 1 + (keyCodes.length * 2) + 4 + 4 + 1 + 4 + 2 + textBytes.length; ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + buffer.order(ByteOrder.BIG_ENDIAN); // Match Python protocol (struct.pack with '>') buffer.put((byte) keyCodes.length); for (int keyCode : keyCodes) { diff --git a/pixi.lock b/pixi.lock index c41ae1a..25ddae4 100644 --- a/pixi.lock +++ b/pixi.lock @@ -376,365 +376,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip - pypi: https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl - pypi: ./ - py310: - channels: - - url: https://conda.anaconda.org/conda-forge/ - - url: https://conda.anaconda.org/pytorch/ - indexes: - - https://pypi.org/simple - packages: - linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.10.16-py310hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/editables-0.5-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py310he8512ff_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h59595ed_1003.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py310h56e06c5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.2.1-h3beb420_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.27.0-pypyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h4637d8d_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.23-h4ddbbb0_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hf1ad2bd_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.84.1-h3618099_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.68.2-h25350d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.0.0-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hd9ff511_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.6.0-cpu_mkl_hc5f969b_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.6-h8d12d68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.7-h024ca30_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py310h89163eb_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py310hb13e2d6_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.6-h53dfc1b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py310h3788b33_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py310h7e6dc6c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.0-h29eaf8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py310hf71b8c6_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.10.16-he725a3c_1_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-5_cp310.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py310_h2c0e51a_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py310h1d65ade_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py310h6c63255_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py310_h91c00cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda - - pypi: https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz - - pypi: https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/b8/a88b8d94e4aa048057e394140514158390fd485c9cc5686072688d4b5385/daemoniker-0.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/ef/09b53c076943c3722bac91a274825ece90985c1b3686d8426608fd741e30/gym-0.22.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/25/26/d786c6bec30fe6110fd3d22c9a273a2a0e56c0b73b93e25ea1af5a53243b/gym_notices-0.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/e8/facde510585869b5ec694e8e0363ffe4eba067cb357a8398a55f6a1f8023/importlib_resources-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/65/8e/590e20833220eac55b6abcde71d3ae629d38ac1c3543bcc2bfe1f3c2f5d1/lxml-5.3.1-cp310-cp310-manylinux_2_28_x86_64.whl - - pypi: git+https://github.com/thomashopkins32/MineDojo.git?rev=main#03ed9b47aeb1e681d9650982f3a06794530781db - - pypi: https://files.pythonhosted.org/packages/e7/a9/39cf856d03690af6fd570cf40331f1f79acdbb3132a9c35d2c5002f7f30b/multiprocess-0.70.17-py310-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/70/e3/8c4e0d24b46fbf02e6b2dc2da5d18f0c73cfd343a1fb01ae64c788c20e56/Pyro4-4.82-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ad/86/e029bd8a451f145e28530128239be057ca6e701aac46ad0d0000f852d551/serpent-1.41-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip - - pypi: https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl - - pypi: ./ - py311: - channels: - - url: https://conda.anaconda.org/conda-forge/ - - url: https://conda.anaconda.org/pytorch/ - indexes: - - https://pypi.org/simple - packages: - linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.11-py311hd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/editables-0.5-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py311h0f6cedb_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h59595ed_1003.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py311h9789449_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.0.0-h76408a6_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.27.0-pypyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h4637d8d_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.23-h4ddbbb0_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hf1ad2bd_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.84.1-h3618099_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.68.2-h25350d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.0.0-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hd9ff511_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.6.0-cpu_mkl_hc5f969b_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.6-h8d12d68_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.7-h024ca30_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py311h64a7726_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.6-h53dfc1b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py311hd18a35c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py311h1322bbf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.0-h29eaf8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py311hfdbb021_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.11-h9e4cc4f_2_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.11-5_cp311.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py311_hcab61ac_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py311h8f841c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py311hafd3f86_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py311_hfa9c634_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda - - pypi: https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz - - pypi: https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/34/b8/a88b8d94e4aa048057e394140514158390fd485c9cc5686072688d4b5385/daemoniker-0.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/01/ef/09b53c076943c3722bac91a274825ece90985c1b3686d8426608fd741e30/gym-0.22.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/25/26/d786c6bec30fe6110fd3d22c9a273a2a0e56c0b73b93e25ea1af5a53243b/gym_notices-0.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/93/e8/facde510585869b5ec694e8e0363ffe4eba067cb357a8398a55f6a1f8023/importlib_resources-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a5/73/762c319c4906b3db67e4abc7cfe7d66c34996edb6d0e8cb60f462954d662/lxml-5.3.1-cp311-cp311-manylinux_2_28_x86_64.whl - - pypi: git+https://github.com/thomashopkins32/MineDojo.git?rev=main#03ed9b47aeb1e681d9650982f3a06794530781db - - pypi: https://files.pythonhosted.org/packages/b2/07/8cbb75d6cfbe8712d8f7f6a5615f083c6e710ab916b748fbb20373ddb142/multiprocess-0.70.17-py311-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/70/e3/8c4e0d24b46fbf02e6b2dc2da5d18f0c73cfd343a1fb01ae64c788c20e56/Pyro4-4.82-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ad/86/e029bd8a451f145e28530128239be057ca6e701aac46ad0d0000f852d551/serpent-1.41-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip - - pypi: https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl - - pypi: ./ py312: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -1116,16 +757,6 @@ packages: version: 3.4.1 sha256: bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: charset-normalizer - version: 3.4.1 - sha256: fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 - requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: charset-normalizer - version: 3.4.1 - sha256: b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl name: cloudpickle version: 3.1.1 @@ -1164,28 +795,6 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.10.16-py310hd8ed1ab_1.conda - noarch: generic - sha256: 522b5ff2c5b1ebe0050ad15cd76a1e14696752eead790ab28e29977d7a8a99e6 - md5: 5c7fe189f8761cd08a69924554c1ffab - depends: - - python 3.10.16.* - - python_abi * *_cp310 - license: Python-2.0 - purls: [] - size: 48888 - timestamp: 1733407928192 -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.11.11-py311hd8ed1ab_2.conda - noarch: generic - sha256: 52e462716ff6b062bf6992f9e95fcb65a0b95a47db73f0478bd0ceab8a37036a - md5: fb7bc3f1bccb39021a53309e83bce28d - depends: - - python 3.11.11.* - - python_abi * *_cp311 - license: Python-2.0 - purls: [] - size: 46889 - timestamp: 1741034069952 - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.9-py312hd8ed1ab_1.conda noarch: generic sha256: 58a637bc8328b115c9619de3fcd664ec26662083319e3c106917a1b3ee4d7594 @@ -1254,13 +863,6 @@ packages: - pkg:pypi/editables?source=hash-mapping size: 10828 timestamp: 1733208220327 -- pypi: https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl - name: exceptiongroup - version: 1.2.2 - sha256: 3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b - requires_dist: - - pytest>=6 ; extra == 'test' - requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl name: farama-notifications version: 0.0.4 @@ -1422,40 +1024,6 @@ packages: purls: [] size: 460055 timestamp: 1718980856608 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py310he8512ff_3.conda - sha256: 18866d66175a957fd5a61125bb618b160c77c8d08d0d9d5be991e9f77c19b288 - md5: 832c93fd1bee415d2833b023f5ebb2dc - depends: - - __glibc >=2.17,<3.0.a0 - - gmp >=6.3.0,<7.0a0 - - libgcc >=13 - - mpc >=1.3.1,<2.0a0 - - mpfr >=4.2.1,<5.0a0 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - license: LGPL-3.0-or-later - license_family: LGPL - purls: - - pkg:pypi/gmpy2?source=hash-mapping - size: 202700 - timestamp: 1733462653858 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py311h0f6cedb_3.conda - sha256: 27aad6269fb3fcb80170a0c9f2108644b7560c56c88ab2d6da951059593c38ca - md5: 847d4e1e49a6aec4c130b2db001c3802 - depends: - - __glibc >=2.17,<3.0.a0 - - gmp >=6.3.0,<7.0a0 - - libgcc >=13 - - mpc >=1.3.1,<2.0a0 - - mpfr >=4.2.1,<5.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: LGPL-3.0-or-later - license_family: LGPL - purls: - - pkg:pypi/gmpy2?source=hash-mapping - size: 202814 - timestamp: 1733462674178 - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda sha256: addd0bc226ca86c11f1223ab322d12b67501c2b3d93749bdab2068ccaedd8ef0 md5: 673ef4d6611f5b4ca7b5c1f8c65a38dc @@ -1495,40 +1063,6 @@ packages: purls: [] size: 96855 timestamp: 1711634169756 -- conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py310h56e06c5_0.conda - sha256: c9c67e3e46bda0755d6d0f0ab1f0a18853438cf42e070ae14ce77e70ce84993a - md5: 98f025f42da69cd338340ec9b06c63ba - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libgrpc 1.68.2 h25350d4_0 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/grpcio?source=hash-mapping - size: 884523 - timestamp: 1740918642355 -- conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py311h9789449_0.conda - sha256: 923cc2ac12426c9cd91f3d49ec629afcbc4f2cb4d8df76ca8023872b9b695153 - md5: 659bcea199c0117e2be1d2d18a543248 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libgrpc 1.68.2 h25350d4_0 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/grpcio?source=hash-mapping - size: 937506 - timestamp: 1740918771334 - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py312hacea422_0.conda sha256: e245961c8682505ef719ab975503f085a00b1fac1bcb4eb3334919dd8a5e5ce1 md5: 34ce5820aac57bc90820faf96f05ce63 @@ -1565,29 +1099,29 @@ packages: - scipy>=1.4.1 ; extra == 'toy-text' - lz4>=3.1.0 ; extra == 'other' - opencv-python>=3.0 ; extra == 'other' - - pygame==2.1.0 ; extra == 'nomujoco' - lz4>=3.1.0 ; extra == 'nomujoco' - opencv-python>=3.0 ; extra == 'nomujoco' - pygame==2.1.0 ; extra == 'nomujoco' - - scipy>=1.4.1 ; extra == 'nomujoco' - box2d-py==2.3.5 ; extra == 'nomujoco' - pygame==2.1.0 ; extra == 'nomujoco' + - pygame==2.1.0 ; extra == 'nomujoco' + - scipy>=1.4.1 ; extra == 'nomujoco' + - mujoco-py>=1.50,<2.0 ; extra == 'all' + - lz4>=3.1.0 ; extra == 'all' + - opencv-python>=3.0 ; extra == 'all' + - box2d-py==2.3.5 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' + - ale-py~=0.7.4 ; extra == 'all' - lz4>=3.1.0 ; extra == 'all' - opencv-python>=3.0 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - - scipy>=1.4.1 ; extra == 'all' - box2d-py==2.3.5 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - scipy>=1.4.1 ; extra == 'all' - - lz4>=3.1.0 ; extra == 'all' - - opencv-python>=3.0 ; extra == 'all' - - ale-py~=0.7.4 ; extra == 'all' - - box2d-py==2.3.5 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - - mujoco-py>=1.50,<2.0 ; extra == 'all' + - scipy>=1.4.1 ; extra == 'all' requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/25/26/d786c6bec30fe6110fd3d22c9a273a2a0e56c0b73b93e25ea1af5a53243b/gym_notices-0.0.8-py3-none-any.whl name: gym-notices @@ -2397,28 +1931,6 @@ packages: purls: [] size: 3190529 timestamp: 1736986301022 -- pypi: https://files.pythonhosted.org/packages/65/8e/590e20833220eac55b6abcde71d3ae629d38ac1c3543bcc2bfe1f3c2f5d1/lxml-5.3.1-cp310-cp310-manylinux_2_28_x86_64.whl - name: lxml - version: 5.3.1 - sha256: 7c0536bd9178f754b277a3e53f90f9c9454a3bd108b1531ffff720e082d824f2 - requires_dist: - - cython>=3.0.11,<3.1.0 ; extra == 'source' - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/a5/73/762c319c4906b3db67e4abc7cfe7d66c34996edb6d0e8cb60f462954d662/lxml-5.3.1-cp311-cp311-manylinux_2_28_x86_64.whl - name: lxml - version: 5.3.1 - sha256: 1b6f92e35e2658a5ed51c6634ceb5ddae32053182851d8cad2a5bc102a359b33 - requires_dist: - - cython>=3.0.11,<3.1.0 ; extra == 'source' - - cssselect>=0.7 ; extra == 'cssselect' - - html5lib ; extra == 'html5' - - beautifulsoup4 ; extra == 'htmlsoup' - - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.6' - pypi: https://files.pythonhosted.org/packages/c5/f8/309546aec092434166a6e11c7dcecb5c2d0a787c18c072d61e18da9eba57/lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl name: lxml version: 5.3.1 @@ -2442,38 +1954,6 @@ packages: - pkg:pypi/markdown?source=hash-mapping size: 78331 timestamp: 1710435316163 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py310h89163eb_1.conda - sha256: 0bed20ec27dcbcaf04f02b2345358e1161fb338f8423a4ada1cf0f4d46918741 - md5: 8ce3f0332fd6de0d737e2911d329523f - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 23091 - timestamp: 1733219814479 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py311h2dc5d0c_1.conda - sha256: 0291d90706ac6d3eea73e66cd290ef6d805da3fad388d1d476b8536ec92ca9a8 - md5: 6565a715337ae279e351d0abd8ffe88a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - jinja2 >=3.0.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/markupsafe?source=hash-mapping - size: 25354 - timestamp: 1733219879408 - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda sha256: 4a6bf68d2a2b669fecc9a4a009abd1cf8e72c2289522ff00d81b5a6e51ae78f5 md5: eb227c3e0bf58f5bd69c0532b157975b @@ -2512,7 +1992,7 @@ packages: - pypi: ./ name: mineagent version: 0.0.1 - sha256: c5e40579dfa2050008785212eb7c2bc64d79ce6571e35e8010209fb0ee39cfc0 + sha256: 47afe98766c6a76acdcee5477183935d6deee083e2b26e699fe7d7c358c47f8d requires_dist: - pyyaml - dacite @@ -2615,20 +2095,6 @@ packages: requires_dist: - dill>=0.3.9 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/b2/07/8cbb75d6cfbe8712d8f7f6a5615f083c6e710ab916b748fbb20373ddb142/multiprocess-0.70.17-py311-none-any.whl - name: multiprocess - version: 0.70.17 - sha256: 2884701445d0177aec5bd5f6ee0df296773e4fb65b11903b94c613fb46cfb7d1 - requires_dist: - - dill>=0.3.9 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/e7/a9/39cf856d03690af6fd570cf40331f1f79acdbb3132a9c35d2c5002f7f30b/multiprocess-0.70.17-py310-none-any.whl - name: multiprocess - version: 0.70.17 - sha256: 38357ca266b51a2e22841b755d9a91e4bb7b937979a54d411677111716c32744 - requires_dist: - - dill>=0.3.9 - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl name: mypy-extensions version: 1.0.0 @@ -2665,44 +2131,6 @@ packages: version: 1.9.1 sha256: ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py310hb13e2d6_0.conda - sha256: 028fe2ea8e915a0a032b75165f11747770326f3d767e642880540c60a3256425 - md5: 6593de64c935768b6bad3e19b3e978be - depends: - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - libgcc-ng >=12 - - liblapack >=3.9.0,<4.0a0 - - libstdcxx-ng >=12 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - constrains: - - numpy-base <0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/numpy?source=hash-mapping - size: 7009070 - timestamp: 1707225917496 -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py311h64a7726_0.conda - sha256: 3f4365e11b28e244c95ba8579942b0802761ba7bb31c026f50d1a9ea9c728149 - md5: a502d7aad449a1206efb366d6a12c52d - depends: - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - libgcc-ng >=12 - - liblapack >=3.9.0,<4.0a0 - - libstdcxx-ng >=12 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - numpy-base <0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/numpy?source=hash-mapping - size: 8065890 - timestamp: 1707225944355 - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda sha256: fe3459c75cf84dcef6ef14efcc4adb0ade66038ddd27cadb894f34f4797687d8 md5: d8285bea2a350f63fab23bf460221f3f @@ -2804,38 +2232,6 @@ packages: purls: [] size: 2939306 timestamp: 1739301879343 -- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py310h3788b33_0.conda - sha256: a1df5109af64efd456774deb0c28c16083559e1f344c6d098885b7451accebf0 - md5: 96cce14c8f3c09807d98b042042eea9a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - - typing-extensions >=4.5 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/optree?source=hash-mapping - size: 348260 - timestamp: 1740912421533 -- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py311hd18a35c_0.conda - sha256: e6cdaf463c75cb6e4fd301ff1fcba01cf0d56634726ca6e87a7d874a929d07fd - md5: 2cd759b79a672c4402b4f7f6ddbfd16c - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - typing-extensions >=4.5 - license: Apache-2.0 - license_family: Apache - purls: - - pkg:pypi/optree?source=hash-mapping - size: 381260 - timestamp: 1740912334775 - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py312h68727a3_0.conda sha256: 8fd7b4433cc8bef4570904343b562b27da8aa566eae68b6166113a635f07dbb1 md5: 846469b9895b87452453408a51d06a81 @@ -2873,64 +2269,20 @@ packages: purls: - pkg:pypi/pathspec?source=hash-mapping size: 41075 - timestamp: 1733233471940 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - sha256: 27c4014f616326240dcce17b5f3baca3953b6bc5f245ceb49c3fa1e6320571eb - md5: b90bece58b4c2bf25969b70f3be42d25 - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - libgcc >=13 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 1197308 - timestamp: 1745955064657 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py310h7e6dc6c_0.conda - sha256: e11d694b7c12b6a76624e8c3e48892924668a97ec26f353ce37b0648bd12ad87 - md5: 14d300b9e1504748e70cc6499a7b4d25 - depends: - - __glibc >=2.17,<3.0.a0 - - freetype >=2.12.1,<3.0a0 - - lcms2 >=2.16,<3.0a0 - - libgcc >=13 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libtiff >=4.7.0,<4.8.0a0 - - libwebp-base >=1.5.0,<2.0a0 - - libxcb >=1.17.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openjpeg >=2.5.3,<3.0a0 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - - tk >=8.6.13,<8.7.0a0 - license: HPND - purls: - - pkg:pypi/pillow?source=hash-mapping - size: 42419230 - timestamp: 1735929858736 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py311h1322bbf_0.conda - sha256: 71e0ce18201695adec3bbbfbab74e82b0ab05fe8929ad046d2c507a71c8a3c63 - md5: 9f4f5593335f76c1dbf7381c11fe7155 + timestamp: 1733233471940 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda + sha256: 27c4014f616326240dcce17b5f3baca3953b6bc5f245ceb49c3fa1e6320571eb + md5: b90bece58b4c2bf25969b70f3be42d25 depends: - __glibc >=2.17,<3.0.a0 - - freetype >=2.12.1,<3.0a0 - - lcms2 >=2.16,<3.0a0 + - bzip2 >=1.0.8,<2.0a0 - libgcc >=13 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libtiff >=4.7.0,<4.8.0a0 - - libwebp-base >=1.5.0,<2.0a0 - - libxcb >=1.17.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - openjpeg >=2.5.3,<3.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - tk >=8.6.13,<8.7.0a0 - license: HPND - purls: - - pkg:pypi/pillow?source=hash-mapping - size: 42021920 - timestamp: 1735929841160 + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 1197308 + timestamp: 1745955064657 - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py312h80c1187_0.conda sha256: 5c347962202b55ae4d8a463e0555c5c6ca33396266a08284bf1384399894e541 md5: d3894405f05b2c0f351d5de3ae26fa9c @@ -3042,40 +2394,6 @@ packages: - pyyaml>=5.1 - virtualenv>=20.10.0 requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py310hf71b8c6_0.conda - sha256: ce9c1d73b2c972c35a8435eddeab61c70732c96c540f2451584252d55f17eb89 - md5: 084db219a9da4261b3308d120b9b552a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - constrains: - - libprotobuf 5.28.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/protobuf?source=hash-mapping - size: 391863 - timestamp: 1731366782023 -- conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py311hfdbb021_0.conda - sha256: 2d9b2b9a7549e7dd58138cd3211a11893b8f6dee5a1137529623bf92cddba45b - md5: ddf920c3b5d1cbd5ffbea591d2ad09ea - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - libprotobuf 5.28.3 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/protobuf?source=hash-mapping - size: 471398 - timestamp: 1731366737017 - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py312h2ec8cdc_0.conda sha256: acb2e0ee948e3941f8ed191cb77f654e06538638aed8ccd71cbc78a15242ebbb md5: 9d7e427d159c1b2d516cc047ff177c48 @@ -3221,61 +2539,6 @@ packages: - pytest-asyncio ; extra == 'dev' - tox ; extra == 'dev' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.10.16-he725a3c_1_cpython.conda - build_number: 1 - sha256: 3f90a2d5062a73cd2dd8a0027718aee1db93f7975b9cfe529e2c9aeec2db262e - md5: b887811a901b3aa622a92caf03bc8917 - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libffi >=3.4,<4.0a0 - - libgcc >=13 - - liblzma >=5.6.3,<6.0a0 - - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.47.0,<4.0a0 - - libuuid >=2.38.1,<3.0a0 - - libxcrypt >=4.4.36 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.4.0,<4.0a0 - - readline >=8.2,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - constrains: - - python_abi 3.10.* *_cp310 - license: Python-2.0 - purls: [] - size: 25199631 - timestamp: 1733409331823 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.11-h9e4cc4f_2_cpython.conda - build_number: 2 - sha256: e0be7ad95a034d10e021f15317bf5c70fc1161564fa47844984c245505cde36c - md5: 81dd3e521f9b9eaa58d06213e28aaa9b - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.6.4,<3.0a0 - - libffi >=3.4,<4.0a0 - - libgcc >=13 - - liblzma >=5.6.4,<6.0a0 - - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.49.1,<4.0a0 - - libuuid >=2.38.1,<3.0a0 - - libxcrypt >=4.4.36 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.4.1,<4.0a0 - - readline >=8.2,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - constrains: - - python_abi 3.11.* *_cp311 - license: Python-2.0 - purls: [] - size: 30594389 - timestamp: 1741036299726 - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.9-h9e4cc4f_1_cpython.conda build_number: 1 sha256: 77f2073889d4c91a57bc0da73a0466d9164dbcf6191ea9c3a7be6872f784d625 @@ -3311,28 +2574,6 @@ packages: requires_dist: - six>=1.5 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-5_cp310.conda - build_number: 5 - sha256: 074d2f0b31f0333b7e553042b17ea54714b74263f8adda9a68a4bd8c7e219971 - md5: 2921c34715e74b3587b4cff4d36844f9 - constrains: - - python 3.10.* *_cpython - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 6227 - timestamp: 1723823165457 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.11-5_cp311.conda - build_number: 5 - sha256: 2660b8059b3ee854bc5d3c6b1fce946e5bd2fe8fbca7827de2c5885ead6209de - md5: 139a8d40c8a2f430df31048949e450de - constrains: - - python 3.11.* *_cpython - license: BSD-3-Clause - license_family: BSD - purls: [] - size: 6211 - timestamp: 1723823324668 - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda build_number: 5 sha256: d10e93d759931ffb6372b45d65ff34d95c6000c61a07e298d162a3bc2accebb0 @@ -3344,88 +2585,6 @@ packages: purls: [] size: 6238 timestamp: 1723823388266 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py310_h2c0e51a_101.conda - sha256: f0765ca5da173fc4e60e53cae859ca0efadbbf71785bf7adf777abe78a3cebb1 - md5: 51a7717bfa4e1926e0f757d680e94bca - depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex * *_llvm - - _openmp_mutex >=4.5 - - filelock - - fsspec - - jinja2 - - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - - libblas * *mkl - - libcblas >=3.9.0,<4.0a0 - - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 - - libstdcxx >=13 - - libtorch 2.6.0 cpu_mkl_hc5f969b_101 - - libuv >=1.50.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=19.1.7 - - mkl >=2024.2.2,<2025.0a0 - - networkx - - numpy >=1.19,<3 - - optree >=0.13.0 - - pybind11 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - - setuptools - - sleef >=3.8,<4.0a0 - - sympy >=1.13.1,!=1.13.2 - - typing_extensions >=4.10.0 - constrains: - - pytorch-cpu ==2.6.0 - - pytorch-gpu ==99999999 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/torch?source=hash-mapping - size: 24537325 - timestamp: 1741570328624 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py311_hcab61ac_101.conda - sha256: 314e9e5d5f377c1b7a1401b1c4dcc68e53f697ed6bee38e80f3eae1b61727ec4 - md5: 22ac1759e20948028dbc248c7ac3d289 - depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex * *_llvm - - _openmp_mutex >=4.5 - - filelock - - fsspec - - jinja2 - - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - - libblas * *mkl - - libcblas >=3.9.0,<4.0a0 - - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 - - libstdcxx >=13 - - libtorch 2.6.0 cpu_mkl_hc5f969b_101 - - libuv >=1.50.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=19.1.7 - - mkl >=2024.2.2,<2025.0a0 - - networkx - - numpy >=1.19,<3 - - optree >=0.13.0 - - pybind11 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - - setuptools - - sleef >=3.8,<4.0a0 - - sympy >=1.13.1,!=1.13.2 - - typing_extensions >=4.10.0 - constrains: - - pytorch-cpu ==2.6.0 - - pytorch-gpu ==99999999 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/torch?source=hash-mapping - size: 28649699 - timestamp: 1741571932015 - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py312_h446997d_101.conda sha256: 6d1d48022c10b4da0355b3bb7886f391980351d0d7796925c1f853457bf172d8 md5: 47d470ce7ceb3775d3f1a4c00ecef44d @@ -3467,16 +2626,6 @@ packages: - pkg:pypi/torch?source=hash-mapping size: 28335884 timestamp: 1741568724835 -- pypi: https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: pyyaml - version: 6.0.2 - sha256: ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - name: pyyaml - version: 6.0.2 - sha256: 3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 - requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: pyyaml version: 6.0.2 @@ -3520,52 +2669,6 @@ packages: version: 0.9.10 sha256: b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py310h1d65ade_0.conda - sha256: 4cb98641f870666d365594013701d5691205a0fe81ac3ba7778a23b1cc2caa8e - md5: 8c29cd33b64b2eb78597fa28b5595c8d - depends: - - __glibc >=2.17,<3.0.a0 - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - libgcc >=13 - - libgfortran - - libgfortran5 >=13.3.0 - - liblapack >=3.9.0,<4.0a0 - - libstdcxx >=13 - - numpy <2.5 - - numpy >=1.19,<3 - - numpy >=1.23.5 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/scipy?source=hash-mapping - size: 16417101 - timestamp: 1739791865060 -- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py311h8f841c2_0.conda - sha256: 6d0902775e3ff96dd1d36ac627e03fe6c0b3d2159bb71e115dd16a1f31693b25 - md5: 5ec0a1732a05376241e1e4c6d50e0e91 - depends: - - __glibc >=2.17,<3.0.a0 - - libblas >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - - libgcc >=13 - - libgfortran - - libgfortran5 >=13.3.0 - - liblapack >=3.9.0,<4.0a0 - - libstdcxx >=13 - - numpy <2.5 - - numpy >=1.19,<3 - - numpy >=1.23.5 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/scipy?source=hash-mapping - size: 17193126 - timestamp: 1739791897768 - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda sha256: b9faaa024b77a3678a988c5a490f02c4029c0d5903998b585100e05bc7d4ff36 md5: 00b999c5f9d01fb633db819d79186bd4 @@ -3677,40 +2780,6 @@ packages: - pkg:pypi/tensorboard?source=hash-mapping size: 5188674 timestamp: 1740522550333 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py310h6c63255_2.conda - sha256: 7525504f377ffbc9104c338722902fe67f5999efff26f47ab17ec3aa4547146f - md5: fd269768504e55006d52f0a5face4dcf - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - openssl >=3.3.2,<4.0a0 - - python >=3.10,<3.11.0a0 - - python_abi 3.10.* *_cp310 - constrains: - - __glibc >=2.17 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/tensorboard-data-server?source=hash-mapping - size: 3571250 - timestamp: 1728640103310 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py311hafd3f86_2.conda - sha256: b2abc9b9b57c7ea212099fcc30d0e3523fc79a731ec11f69ce5402879c37cb3e - md5: 599c214c5dd150c9dd716db4cdbedf2e - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - openssl >=3.3.2,<4.0a0 - - python >=3.11,<3.12.0a0 - - python_abi 3.11.* *_cp311 - constrains: - - __glibc >=2.17 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/tensorboard-data-server?source=hash-mapping - size: 3571735 - timestamp: 1728640114903 - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312hda17c39_2.conda sha256: 9dc3ebf51a96a1c2b82b2d8907030453b797141a8cb0bdaa66c67c7b622d28c3 md5: 8f4e72393be23b3eaa7bbc3cd4a71c40 @@ -3750,57 +2819,6 @@ packages: - pkg:pypi/tomli?source=hash-mapping size: 19167 timestamp: 1733256819729 -- conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py310_h91c00cc_0.conda - sha256: 1dcab7c9bf2087073d232680cf63fc6c6cc9536cd975eaf90cc498e8002c305c - md5: cc5609172de0067fe91b43c4d610dc23 - depends: - - python - - pytorch * cpu* - - pillow >=5.3.0,!=8.3.0,!=8.3.1 - - numpy >=1.23.5 - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - - libgcc >=13 - - libtorch >=2.6.0,<2.7.0a0 - - pytorch >=2.6.0,<2.7.0a0 - - libtorch >=2.6.0,<2.7.0a0 - - libpng >=1.6.46,<1.7.0a0 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libwebp-base >=1.5.0,<2.0a0 - - python_abi 3.10.* *_cp310 - - giflib >=5.2.2,<5.3.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/torchvision?source=hash-mapping - size: 1197493 - timestamp: 1739668812897 -- conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py311_hfa9c634_0.conda - sha256: 99c221b69e7ba548e78989f1e1a68fcdadefee2b0aa9cc2e72c3a0bdf9345ade - md5: 12aa45f589ed34a92269cae197a72716 - depends: - - python - - pytorch * cpu* - - pillow >=5.3.0,!=8.3.0,!=8.3.1 - - numpy >=1.23.5 - - __glibc >=2.17,<3.0.a0 - - libstdcxx >=13 - - libgcc >=13 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libwebp-base >=1.5.0,<2.0a0 - - libtorch >=2.6.0,<2.7.0a0 - - python_abi 3.11.* *_cp311 - - libpng >=1.6.46,<1.7.0a0 - - pytorch >=2.6.0,<2.7.0a0 - - libtorch >=2.6.0,<2.7.0a0 - - giflib >=5.2.2,<5.3.0a0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/torchvision?source=hash-mapping - size: 1466916 - timestamp: 1739668773989 - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py312_h11dcf35_0.conda sha256: a4ca6214fa63e97567fba8df5f03ff2a32fceca67f75c3b0fb55c11dd5b215b7 md5: 2498c29ec4e9742141697fcda2cdf9f1 diff --git a/pyproject.toml b/pyproject.toml index cf474ab..b0b1b57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,14 +58,12 @@ typeCheckingMode = "standard" reportMissingImports = true exclude = ["**/build"] -[tool.pixi.project] +[tool.pixi.workspace] channels = ["conda-forge", "pytorch"] platforms = ["linux-64"] -[tool.pixi.host-dependencies] -hatchling = "*" - [tool.pixi.dependencies] +hatchling = "*" pytorch = "*" torchvision = "*" numpy = "==1.26.4" @@ -82,20 +80,13 @@ MineDojo = { git = "https://github.com/thomashopkins32/MineDojo.git", rev = "mai [tool.pixi.environments] default = { solve-group = "default" } dev = { features = ["dev"], solve-group = "default" } -py310 = { features = ["py310"] } -py311 = { features = ["py311"] } py312 = { features = ["py312"] } -[tool.pixi.feature.py310.dependencies] -python = "3.10.*" - -[tool.pixi.feature.py311.dependencies] -python = "3.11.*" - [tool.pixi.feature.py312.dependencies] python = "3.12.*" [tool.pixi.tasks] mineagent = "mineagent" -minecraft-client = "cd forge && gradle runClient" -minecraft-build = "cd forge && gradle build" +gradle-run-client = "cd forge && gradle runClient" +gradle-build = "cd forge && gradle build" +gradle-test = "cd forge && gradle test" \ No newline at end of file From ff47a9a299ec5653acd99f4129c3d0c87bed3f8a Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 24 Jan 2026 12:20:32 -0500 Subject: [PATCH 15/21] Handle input modifiers better --- .../java/com/mineagent/InputInjector.java | 83 +++++++++++++++---- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/forge/src/main/java/com/mineagent/InputInjector.java b/forge/src/main/java/com/mineagent/InputInjector.java index 18f2650..0c2393d 100644 --- a/forge/src/main/java/com/mineagent/InputInjector.java +++ b/forge/src/main/java/com/mineagent/InputInjector.java @@ -2,6 +2,7 @@ import com.mojang.logging.LogUtils; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @@ -16,9 +17,23 @@ */ public class InputInjector { private static final Logger LOGGER = LogUtils.getLogger(); + + // Modifier keys + private static final Set MODIFIER_KEYS = new HashSet<>(Arrays.asList( + GLFW.GLFW_KEY_LEFT_SHIFT, + GLFW.GLFW_KEY_RIGHT_SHIFT, + GLFW.GLFW_KEY_LEFT_CONTROL, + GLFW.GLFW_KEY_RIGHT_CONTROL, + GLFW.GLFW_KEY_LEFT_ALT, + GLFW.GLFW_KEY_RIGHT_ALT, + GLFW.GLFW_KEY_LEFT_SUPER, + GLFW.GLFW_KEY_RIGHT_SUPER, + GLFW.GLFW_KEY_MENU + )); // Key state tracking for press/release detection private Set previouslyPressedKeys = new HashSet<>(); + private int previousModifiers = 0; // Mouse button state tracking (bits: 0=left, 1=right, 2=middle) private byte previousMouseButtons = 0; @@ -67,29 +82,36 @@ private void handleKeyboardInput(Minecraft mc, long window, int[] keyCodes) { .boxed() .collect(Collectors.toSet()); + // Find modifier keys and non-modifier keys + Set modifierKeys = findModifierKeys(currentKeys); + Set nonModifierKeys = currentKeys.stream() + .filter(key -> !modifierKeys.contains(key)) + .collect(Collectors.toSet()); + int modifiers = computeModifiers(modifierKeys); + // Release keys that were pressed but are no longer for (int key : previouslyPressedKeys) { - if (!currentKeys.contains(key)) { - fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE); + if (!nonModifierKeys.contains(key)) { + fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE, previousModifiers); } } // Press keys that are newly pressed - for (int key : currentKeys) { + for (int key : nonModifierKeys) { if (!previouslyPressedKeys.contains(key)) { - fireKeyEvent(mc, window, key, GLFW.GLFW_PRESS); + fireKeyEvent(mc, window, key, GLFW.GLFW_PRESS, modifiers); } } - previouslyPressedKeys = currentKeys; + previouslyPressedKeys = nonModifierKeys; + previousModifiers = modifiers; } /** * Fires a key event through Minecraft's KeyboardHandler. */ - private void fireKeyEvent(Minecraft mc, long window, int keyCode, int action) { + private void fireKeyEvent(Minecraft mc, long window, int keyCode, int action, int modifiers) { int scanCode = GLFW.glfwGetKeyScancode(keyCode); - int modifiers = computeModifiers(); LOGGER.debug("Firing key event: keyCode={}, scanCode={}, action={}, mods={}", keyCode, scanCode, action == GLFW.GLFW_PRESS ? "PRESS" : "RELEASE", modifiers); @@ -97,22 +119,39 @@ private void fireKeyEvent(Minecraft mc, long window, int keyCode, int action) { // Call the same handler that GLFW callbacks use mc.keyboardHandler.keyPress(window, keyCode, scanCode, action, modifiers); } + + /* + * Finds all modifier keys in a set of keys. + * + * @param keys The set of keys to search + * @return The set of modifier keys, or an empty set if every key is a modifier + */ + private Set findModifierKeys(Set keys) { + Set modifiers = keys.stream() + .filter(MODIFIER_KEYS::contains) + .collect(Collectors.toSet()); + // If every key is a modifier, treat as "not used as modifier" so return empty set + if (!modifiers.isEmpty() && modifiers.size() == keys.size()) { + return Collections.emptySet(); + } + return modifiers; + } /** * Computes current modifier key state based on pressed keys. */ - private int computeModifiers() { + private int computeModifiers(Set modifierKeys) { int mods = 0; - if (previouslyPressedKeys.contains(GLFW.GLFW_KEY_LEFT_SHIFT) || - previouslyPressedKeys.contains(GLFW.GLFW_KEY_RIGHT_SHIFT)) { + if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_SHIFT) || + modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_SHIFT)) { mods |= GLFW.GLFW_MOD_SHIFT; } - if (previouslyPressedKeys.contains(GLFW.GLFW_KEY_LEFT_CONTROL) || - previouslyPressedKeys.contains(GLFW.GLFW_KEY_RIGHT_CONTROL)) { + if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_CONTROL) || + modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_CONTROL)) { mods |= GLFW.GLFW_MOD_CONTROL; } - if (previouslyPressedKeys.contains(GLFW.GLFW_KEY_LEFT_ALT) || - previouslyPressedKeys.contains(GLFW.GLFW_KEY_RIGHT_ALT)) { + if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_ALT) || + modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_ALT)) { mods |= GLFW.GLFW_MOD_ALT; } return mods; @@ -171,7 +210,7 @@ private void handleMouseMovement(Minecraft mc, long window, float deltaX, float * and only fire release events on actual release transitions. */ private void handleMouseButtons(Minecraft mc, long window, byte currentButtons) { - int modifiers = computeModifiers(); + int modifiers = previousModifiers; // Check each button (0=left, 1=right, 2=middle) for (int button = 0; button < 3; button++) { @@ -238,18 +277,19 @@ public void reset() { // Release all pressed keys for (int key : previouslyPressedKeys) { - fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE); + fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE, previousModifiers); } // Release all pressed mouse buttons for (int button = 0; button < 3; button++) { if ((previousMouseButtons & (1 << button)) != 0) { - mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, 0); + mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, previousModifiers); } } } previouslyPressedKeys.clear(); + previousModifiers = 0; previousMouseButtons = 0; mouseInitialized = false; @@ -276,6 +316,13 @@ public double getVirtualMouseY() { public Set getPressedKeys() { return new HashSet<>(previouslyPressedKeys); } + + /** + * Gets the current modifier key state. + */ + public int getModifiers() { + return previousModifiers; + } /** * Gets the current mouse button state. @@ -297,7 +344,7 @@ public void maintainButtonState() { // If any buttons are held, fire press events to maintain the state if (previousMouseButtons != 0) { long window = mc.getWindow().getWindow(); - int modifiers = computeModifiers(); + int modifiers = previousModifiers; for (int button = 0; button < 3; button++) { if ((previousMouseButtons & (1 << button)) != 0) { From 2631fc47950004b6f72b6630eb0fa77dba119da2 Mon Sep 17 00:00:00 2001 From: Thomas Hopkins Date: Sat, 24 Jan 2026 12:31:50 -0500 Subject: [PATCH 16/21] First pass at generated unit tests (need to check) --- forge/build.gradle | 4 + .../java/com/mineagent/InputInjectorTest.java | 458 ++++++++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 forge/src/test/java/com/mineagent/InputInjectorTest.java diff --git a/forge/build.gradle b/forge/build.gradle index f731666..d59219e 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -132,6 +132,10 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // Mockito for mocking in tests + testImplementation 'org.mockito:mockito-core:5.14.2' + testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2' + // Example mod dependency with JEI // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime // compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}" diff --git a/forge/src/test/java/com/mineagent/InputInjectorTest.java b/forge/src/test/java/com/mineagent/InputInjectorTest.java new file mode 100644 index 0000000..ff71764 --- /dev/null +++ b/forge/src/test/java/com/mineagent/InputInjectorTest.java @@ -0,0 +1,458 @@ +package com.mineagent; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import com.mojang.blaze3d.platform.Window; +import net.minecraft.client.KeyboardHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.MouseHandler; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Options; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.player.LocalPlayer; +import java.lang.reflect.Field; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.lwjgl.glfw.GLFW; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class InputInjectorTest { + + @Mock(lenient = true) private Minecraft mc; + @Mock(lenient = true) private Window window; + @Mock(lenient = true) private KeyboardHandler keyboardHandler; + @Mock(lenient = true) private MouseHandler mouseHandler; + @Mock(lenient = true) private Screen screen; + @Mock(lenient = true) private Options options; + @Mock(lenient = true) private KeyMapping keyAttack; + @Mock(lenient = true) private KeyMapping keyUse; + + private InputInjector injector; + private static final long WINDOW_HANDLE = 12345L; + + @BeforeEach + void setUp() throws Exception { + injector = new InputInjector(); + + // Setup common mock behavior + when(window.getWindow()).thenReturn(WINDOW_HANDLE); + when(mc.getWindow()).thenReturn(window); + + // Set fields using reflection since they're public fields, not methods + // We need to set them on the mock's class, not the mock itself + try { + Field keyboardHandlerField = Minecraft.class.getField("keyboardHandler"); + keyboardHandlerField.setAccessible(true); + keyboardHandlerField.set(mc, keyboardHandler); + + Field mouseHandlerField = Minecraft.class.getField("mouseHandler"); + mouseHandlerField.setAccessible(true); + mouseHandlerField.set(mc, mouseHandler); + + Field optionsField = Minecraft.class.getField("options"); + optionsField.setAccessible(true); + optionsField.set(mc, options); + + // Set up KeyMapping fields in options + try { + Field keyAttackField = Options.class.getField("keyAttack"); + keyAttackField.setAccessible(true); + keyAttackField.set(options, keyAttack); + + Field keyUseField = Options.class.getField("keyUse"); + keyUseField.setAccessible(true); + keyUseField.set(options, keyUse); + } catch (Exception e) { + // If fields don't exist, continue anyway + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // If fields don't exist or can't be accessed, tests will need to handle this + // This might happen if Minecraft structure changes + } + } + + @Test + void inject_withNewKeyPressed_firesKeyPressEvent() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + RawInput input = new RawInput(new int[] {GLFW.GLFW_KEY_W}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + + injector.inject(input); + + ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Integer.class); + + verify(keyboardHandler, times(1)) + .keyPress( + eq(WINDOW_HANDLE), + keyCodeCaptor.capture(), + anyInt(), + actionCaptor.capture(), + eq(0)); + + assertEquals(GLFW.GLFW_KEY_W, keyCodeCaptor.getValue()); + assertEquals(GLFW.GLFW_PRESS, actionCaptor.getValue()); + } + } + + @Test + void inject_withKeyReleased_firesKeyReleaseEvent() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + // First inject with W key (press) + RawInput pressInput = new RawInput(new int[] {GLFW.GLFW_KEY_W}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + injector.inject(pressInput); + + // Then inject with no keys (release) + RawInput releaseInput = new RawInput(new int[] {}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + injector.inject(releaseInput); + + ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Integer.class); + + // Verify release was called + verify(keyboardHandler, atLeastOnce()) + .keyPress( + eq(WINDOW_HANDLE), + keyCodeCaptor.capture(), + anyInt(), + actionCaptor.capture(), + anyInt()); + + // Check that the last call was a release + assertEquals(GLFW.GLFW_KEY_W, keyCodeCaptor.getValue()); + assertEquals(GLFW.GLFW_RELEASE, actionCaptor.getValue()); + } + } + + @Test + void inject_withModifierAndRegularKey_separatesModifiers() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + RawInput input = + new RawInput( + new int[] {GLFW.GLFW_KEY_LEFT_SHIFT, GLFW.GLFW_KEY_W}, + 0.0f, + 0.0f, + (byte) 0, + 0.0f, + ""); + + injector.inject(input); + + ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor modifiersCaptor = ArgumentCaptor.forClass(Integer.class); + + // Should only fire W key, not Shift + verify(keyboardHandler, times(1)) + .keyPress( + eq(WINDOW_HANDLE), + keyCodeCaptor.capture(), + anyInt(), + eq(GLFW.GLFW_PRESS), + modifiersCaptor.capture()); + + assertEquals(GLFW.GLFW_KEY_W, keyCodeCaptor.getValue()); + // Verify Shift modifier flag is set + assertEquals(GLFW.GLFW_MOD_SHIFT, modifiersCaptor.getValue() & GLFW.GLFW_MOD_SHIFT); + } + } + + @Test + void inject_withOnlyModifierKey_firesAsRegularKey() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + RawInput input = + new RawInput(new int[] {GLFW.GLFW_KEY_LEFT_SHIFT}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + + injector.inject(input); + + ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); + + // Shift should fire as a regular key when pressed alone + verify(keyboardHandler, times(1)) + .keyPress( + eq(WINDOW_HANDLE), + keyCodeCaptor.capture(), + anyInt(), + eq(GLFW.GLFW_PRESS), + eq(0)); + + assertEquals(GLFW.GLFW_KEY_LEFT_SHIFT, keyCodeCaptor.getValue()); + } + } + + @Test + void inject_withMouseButtonPressed_firesMousePressEvent() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + byte leftButton = 0b00000001; // Left mouse button (bit 0) + RawInput input = new RawInput(new int[] {}, 0.0f, 0.0f, leftButton, 0.0f, ""); + + injector.inject(input); + + verify(mouseHandler, times(1)) + .onPress(eq(WINDOW_HANDLE), eq(0), eq(GLFW.GLFW_PRESS), anyInt()); + } + } + + @Test + void inject_withMouseButtonReleased_firesMouseReleaseEvent() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + // First inject with left mouse button (press) + byte leftButton = 0b00000001; + RawInput pressInput = new RawInput(new int[] {}, 0.0f, 0.0f, leftButton, 0.0f, ""); + injector.inject(pressInput); + + // Then inject with no mouse buttons (release) + RawInput releaseInput = new RawInput(new int[] {}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + injector.inject(releaseInput); + + ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Integer.class); + + // Verify release was called + verify(mouseHandler, atLeastOnce()) + .onPress(eq(WINDOW_HANDLE), eq(0), actionCaptor.capture(), anyInt()); + + // Check that release was called + assertTrue( + actionCaptor.getAllValues().contains(GLFW.GLFW_RELEASE), + "Mouse release event should have been fired"); + } + } + + @Test + void inject_withMouseMovement_rotatesPlayer() throws Exception { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + // Create player mock manually to avoid initialization issues + // Use lenient settings and try to mock, but if it fails, skip this test + LocalPlayer player; + try { + player = mock(LocalPlayer.class, withSettings().lenient()); + } catch (Exception e) { + // If LocalPlayer can't be mocked, skip this test + return; + } + // Set player field using reflection + try { + Field playerField = Minecraft.class.getField("player"); + playerField.setAccessible(true); + playerField.set(mc, player); + } catch (Exception e) { + // If field can't be set, skip this test + return; + } + // Set screen to null using reflection + try { + Field screenField = Minecraft.class.getField("screen"); + screenField.setAccessible(true); + screenField.set(mc, null); + } catch (Exception e) { + // Continue anyway + } + // Mock sensitivity() to return an OptionInstance that returns 0.5 + net.minecraft.client.OptionInstance sensitivityOption = + mock(net.minecraft.client.OptionInstance.class); + when(sensitivityOption.get()).thenReturn(0.5); + when(options.sensitivity()).thenReturn(sensitivityOption); + + float mouseDx = 10.0f; + float mouseDy = -5.0f; + RawInput input = new RawInput(new int[] {}, mouseDx, mouseDy, (byte) 0, 0.0f, ""); + + injector.inject(input); + + ArgumentCaptor yawCaptor = ArgumentCaptor.forClass(Double.class); + ArgumentCaptor pitchCaptor = ArgumentCaptor.forClass(Double.class); + + verify(player, times(1)).turn(yawCaptor.capture(), pitchCaptor.capture()); + + // Verify deltas are non-zero and have correct signs + assertNotEquals(0.0, yawCaptor.getValue()); + assertNotEquals(0.0, pitchCaptor.getValue()); + assertTrue(yawCaptor.getValue() > 0, "Yaw should be positive for positive mouseDx"); + assertTrue(pitchCaptor.getValue() < 0, "Pitch should be negative for negative mouseDy"); + } + } + + @Test + void reset_withPressedKeys_releasesAllKeys() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + // Inject some keys and mouse buttons + RawInput input = + new RawInput( + new int[] {GLFW.GLFW_KEY_W, GLFW.GLFW_KEY_A}, + 0.0f, + 0.0f, + (byte) 0b00000011, // Left and right mouse buttons + 0.0f, + ""); + injector.inject(input); + + // Reset + injector.reset(); + + // Verify all keys were released + ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); + verify(keyboardHandler, atLeast(2)) + .keyPress( + eq(WINDOW_HANDLE), + keyCodeCaptor.capture(), + anyInt(), + eq(GLFW.GLFW_RELEASE), + anyInt()); + + // Verify all mouse buttons were released + verify(mouseHandler, atLeastOnce()) + .onPress(eq(WINDOW_HANDLE), eq(0), eq(GLFW.GLFW_RELEASE), anyInt()); + verify(mouseHandler, atLeastOnce()) + .onPress(eq(WINDOW_HANDLE), eq(1), eq(GLFW.GLFW_RELEASE), anyInt()); + + // Verify state is cleared + assertTrue(injector.getPressedKeys().isEmpty(), "Pressed keys should be empty after reset"); + assertEquals(0, injector.getModifiers(), "Modifiers should be 0 after reset"); + assertEquals(0, injector.getMouseButtons(), "Mouse buttons should be 0 after reset"); + } + } + + @Test + void getPressedKeys_afterKeyPress_returnsPressedKeys() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + RawInput input = + new RawInput( + new int[] {GLFW.GLFW_KEY_W, GLFW.GLFW_KEY_A}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + + injector.inject(input); + + var pressedKeys = injector.getPressedKeys(); + + assertEquals(2, pressedKeys.size()); + assertTrue(pressedKeys.contains(GLFW.GLFW_KEY_W)); + assertTrue(pressedKeys.contains(GLFW.GLFW_KEY_A)); + } + } + + @Test + void inject_withTextInput_whenScreenOpen_typesCharacters() throws Exception { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + // Set screen field using reflection + try { + Field screenField = Minecraft.class.getField("screen"); + screenField.setAccessible(true); + screenField.set(mc, screen); + } catch (Exception e) { + // If field can't be set, skip this test + return; + } + + String text = "Hello"; + RawInput input = new RawInput(new int[] {}, 0.0f, 0.0f, (byte) 0, 0.0f, text); + + injector.inject(input); + + ArgumentCaptor charCaptor = ArgumentCaptor.forClass(Character.class); + + // Verify each character was typed + verify(screen, times(text.length())).charTyped(charCaptor.capture(), eq(0)); + + var capturedChars = charCaptor.getAllValues(); + assertEquals('H', capturedChars.get(0)); + assertEquals('e', capturedChars.get(1)); + assertEquals('l', capturedChars.get(2)); + assertEquals('l', capturedChars.get(3)); + assertEquals('o', capturedChars.get(4)); + } + } + + @Test + void inject_withTextInput_whenNoScreen_ignoresText() throws Exception { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + // Set screen field to null using reflection + try { + Field screenField = Minecraft.class.getField("screen"); + screenField.setAccessible(true); + screenField.set(mc, null); + } catch (Exception e) { + // If field can't be set, skip this test + return; + } + + String text = "Hello"; + RawInput input = new RawInput(new int[] {}, 0.0f, 0.0f, (byte) 0, 0.0f, text); + + injector.inject(input); + + // Should not call charTyped when no screen is open + verify(screen, never()).charTyped(anyChar(), anyInt()); + } + } + + @Test + void inject_whenMinecraftNotInitialized_doesNotCrash() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(null); + + RawInput input = new RawInput(new int[] {GLFW.GLFW_KEY_W}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + + // Should not throw exception + assertDoesNotThrow(() -> injector.inject(input)); + } + } + + @Test + void maintainButtonState_withHeldButton_firesPressEvents() { + try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { + mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + + // Ensure all fields are set up properly + try { + Field mouseHandlerField = Minecraft.class.getField("mouseHandler"); + mouseHandlerField.setAccessible(true); + mouseHandlerField.set(mc, mouseHandler); + } catch (Exception e) { + // If field can't be set, skip this test + return; + } + + // Make sure window is set up + when(mc.getWindow()).thenReturn(window); + when(window.getWindow()).thenReturn(WINDOW_HANDLE); + + // Inject with left mouse button held + byte leftButton = 0b00000001; + RawInput input = new RawInput(new int[] {}, 0.0f, 0.0f, leftButton, 0.0f, ""); + injector.inject(input); + + // Reset the mock to clear previous invocations + reset(mouseHandler); + + // Call maintainButtonState - this should not throw + assertDoesNotThrow(() -> injector.maintainButtonState()); + + // Verify mouse button press event was fired by maintainButtonState + verify(mouseHandler, atLeastOnce()) + .onPress(eq(WINDOW_HANDLE), eq(0), eq(GLFW.GLFW_PRESS), anyInt()); + } + } +} From 204daeca3974fecce6a2b7b78652584c17679e44 Mon Sep 17 00:00:00 2001 From: thomashopkins32 Date: Sun, 25 Jan 2026 16:35:31 -0500 Subject: [PATCH 17/21] Rework some tests after review; Remove test after failure to mock LocalPlayer --- .../java/com/mineagent/InputInjectorTest.java | 190 +++++------------- .../com/mineagent/MinecraftTestFixture.java | 162 +++++++++++++++ 2 files changed, 209 insertions(+), 143 deletions(-) create mode 100644 forge/src/test/java/com/mineagent/MinecraftTestFixture.java diff --git a/forge/src/test/java/com/mineagent/InputInjectorTest.java b/forge/src/test/java/com/mineagent/InputInjectorTest.java index ff71764..c1b0532 100644 --- a/forge/src/test/java/com/mineagent/InputInjectorTest.java +++ b/forge/src/test/java/com/mineagent/InputInjectorTest.java @@ -4,84 +4,31 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import com.mojang.blaze3d.platform.Window; -import net.minecraft.client.KeyboardHandler; import net.minecraft.client.Minecraft; -import net.minecraft.client.MouseHandler; -import net.minecraft.client.KeyMapping; -import net.minecraft.client.Options; -import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.player.LocalPlayer; -import java.lang.reflect.Field; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.lwjgl.glfw.GLFW; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.quality.Strictness; -@ExtendWith(MockitoExtension.class) class InputInjectorTest { - @Mock(lenient = true) private Minecraft mc; - @Mock(lenient = true) private Window window; - @Mock(lenient = true) private KeyboardHandler keyboardHandler; - @Mock(lenient = true) private MouseHandler mouseHandler; - @Mock(lenient = true) private Screen screen; - @Mock(lenient = true) private Options options; - @Mock(lenient = true) private KeyMapping keyAttack; - @Mock(lenient = true) private KeyMapping keyUse; - + private MinecraftTestFixture fixture; private InputInjector injector; private static final long WINDOW_HANDLE = 12345L; @BeforeEach - void setUp() throws Exception { + void setUp() { + fixture = new MinecraftTestFixture(WINDOW_HANDLE); injector = new InputInjector(); - - // Setup common mock behavior - when(window.getWindow()).thenReturn(WINDOW_HANDLE); - when(mc.getWindow()).thenReturn(window); - - // Set fields using reflection since they're public fields, not methods - // We need to set them on the mock's class, not the mock itself - try { - Field keyboardHandlerField = Minecraft.class.getField("keyboardHandler"); - keyboardHandlerField.setAccessible(true); - keyboardHandlerField.set(mc, keyboardHandler); - - Field mouseHandlerField = Minecraft.class.getField("mouseHandler"); - mouseHandlerField.setAccessible(true); - mouseHandlerField.set(mc, mouseHandler); - - Field optionsField = Minecraft.class.getField("options"); - optionsField.setAccessible(true); - optionsField.set(mc, options); - - // Set up KeyMapping fields in options - try { - Field keyAttackField = Options.class.getField("keyAttack"); - keyAttackField.setAccessible(true); - keyAttackField.set(options, keyAttack); - - Field keyUseField = Options.class.getField("keyUse"); - keyUseField.setAccessible(true); - keyUseField.set(options, keyUse); - } catch (Exception e) { - // If fields don't exist, continue anyway - } - } catch (NoSuchFieldException | IllegalAccessException e) { - // If fields don't exist or can't be accessed, tests will need to handle this - // This might happen if Minecraft structure changes - } } @Test void inject_withNewKeyPressed_firesKeyPressEvent() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); RawInput input = new RawInput(new int[] {GLFW.GLFW_KEY_W}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); @@ -90,7 +37,7 @@ void inject_withNewKeyPressed_firesKeyPressEvent() { ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Integer.class); - verify(keyboardHandler, times(1)) + verify(fixture.getKeyboardHandler(), times(1)) .keyPress( eq(WINDOW_HANDLE), keyCodeCaptor.capture(), @@ -106,7 +53,7 @@ void inject_withNewKeyPressed_firesKeyPressEvent() { @Test void inject_withKeyReleased_firesKeyReleaseEvent() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); // First inject with W key (press) RawInput pressInput = new RawInput(new int[] {GLFW.GLFW_KEY_W}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); @@ -120,7 +67,7 @@ void inject_withKeyReleased_firesKeyReleaseEvent() { ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Integer.class); // Verify release was called - verify(keyboardHandler, atLeastOnce()) + verify(fixture.getKeyboardHandler(), atLeastOnce()) .keyPress( eq(WINDOW_HANDLE), keyCodeCaptor.capture(), @@ -137,7 +84,7 @@ void inject_withKeyReleased_firesKeyReleaseEvent() { @Test void inject_withModifierAndRegularKey_separatesModifiers() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); RawInput input = new RawInput( @@ -154,7 +101,7 @@ void inject_withModifierAndRegularKey_separatesModifiers() { ArgumentCaptor modifiersCaptor = ArgumentCaptor.forClass(Integer.class); // Should only fire W key, not Shift - verify(keyboardHandler, times(1)) + verify(fixture.getKeyboardHandler(), times(1)) .keyPress( eq(WINDOW_HANDLE), keyCodeCaptor.capture(), @@ -171,7 +118,7 @@ void inject_withModifierAndRegularKey_separatesModifiers() { @Test void inject_withOnlyModifierKey_firesAsRegularKey() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); RawInput input = new RawInput(new int[] {GLFW.GLFW_KEY_LEFT_SHIFT}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); @@ -181,7 +128,7 @@ void inject_withOnlyModifierKey_firesAsRegularKey() { ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); // Shift should fire as a regular key when pressed alone - verify(keyboardHandler, times(1)) + verify(fixture.getKeyboardHandler(), times(1)) .keyPress( eq(WINDOW_HANDLE), keyCodeCaptor.capture(), @@ -196,14 +143,14 @@ void inject_withOnlyModifierKey_firesAsRegularKey() { @Test void inject_withMouseButtonPressed_firesMousePressEvent() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); byte leftButton = 0b00000001; // Left mouse button (bit 0) RawInput input = new RawInput(new int[] {}, 0.0f, 0.0f, leftButton, 0.0f, ""); injector.inject(input); - verify(mouseHandler, times(1)) + verify(fixture.getMouseHandler(), times(1)) .onPress(eq(WINDOW_HANDLE), eq(0), eq(GLFW.GLFW_PRESS), anyInt()); } } @@ -211,7 +158,7 @@ void inject_withMouseButtonPressed_firesMousePressEvent() { @Test void inject_withMouseButtonReleased_firesMouseReleaseEvent() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); // First inject with left mouse button (press) byte leftButton = 0b00000001; @@ -225,51 +172,35 @@ void inject_withMouseButtonReleased_firesMouseReleaseEvent() { ArgumentCaptor actionCaptor = ArgumentCaptor.forClass(Integer.class); // Verify release was called - verify(mouseHandler, atLeastOnce()) + verify(fixture.getMouseHandler(), atLeastOnce()) .onPress(eq(WINDOW_HANDLE), eq(0), actionCaptor.capture(), anyInt()); // Check that release was called assertTrue( - actionCaptor.getAllValues().contains(GLFW.GLFW_RELEASE), - "Mouse release event should have been fired"); + actionCaptor.getValue().equals(GLFW.GLFW_RELEASE), + "Mouse release event should have been fired last"); } } + /* + * TODO: Can't mock LocalPlayer due to static initialization issues. + * Need to rework code to use dependency injection to allow for easier testing. @Test void inject_withMouseMovement_rotatesPlayer() throws Exception { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); // Create player mock manually to avoid initialization issues - // Use lenient settings and try to mock, but if it fails, skip this test - LocalPlayer player; - try { - player = mock(LocalPlayer.class, withSettings().lenient()); - } catch (Exception e) { - // If LocalPlayer can't be mocked, skip this test - return; - } - // Set player field using reflection - try { - Field playerField = Minecraft.class.getField("player"); - playerField.setAccessible(true); - playerField.set(mc, player); - } catch (Exception e) { - // If field can't be set, skip this test - return; - } - // Set screen to null using reflection - try { - Field screenField = Minecraft.class.getField("screen"); - screenField.setAccessible(true); - screenField.set(mc, null); - } catch (Exception e) { - // Continue anyway - } + LocalPlayer player = mock(LocalPlayer.class, withSettings().strictness(Strictness.LENIENT)); + // Set player field using fixture helper + fixture.setMinecraftField("player", player); + // Set screen to null using fixture helper + fixture.setMinecraftField("screen", null); + // Mock sensitivity() to return an OptionInstance that returns 0.5 net.minecraft.client.OptionInstance sensitivityOption = - mock(net.minecraft.client.OptionInstance.class); + mock(net.minecraft.client.OptionInstance.class, withSettings().strictness(Strictness.LENIENT)); when(sensitivityOption.get()).thenReturn(0.5); - when(options.sensitivity()).thenReturn(sensitivityOption); + when(fixture.getOptions().sensitivity()).thenReturn(sensitivityOption); float mouseDx = 10.0f; float mouseDy = -5.0f; @@ -289,11 +220,12 @@ void inject_withMouseMovement_rotatesPlayer() throws Exception { assertTrue(pitchCaptor.getValue() < 0, "Pitch should be negative for negative mouseDy"); } } + */ @Test void reset_withPressedKeys_releasesAllKeys() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); // Inject some keys and mouse buttons RawInput input = @@ -311,7 +243,7 @@ void reset_withPressedKeys_releasesAllKeys() { // Verify all keys were released ArgumentCaptor keyCodeCaptor = ArgumentCaptor.forClass(Integer.class); - verify(keyboardHandler, atLeast(2)) + verify(fixture.getKeyboardHandler(), atLeast(2)) .keyPress( eq(WINDOW_HANDLE), keyCodeCaptor.capture(), @@ -320,9 +252,9 @@ void reset_withPressedKeys_releasesAllKeys() { anyInt()); // Verify all mouse buttons were released - verify(mouseHandler, atLeastOnce()) + verify(fixture.getMouseHandler(), atLeastOnce()) .onPress(eq(WINDOW_HANDLE), eq(0), eq(GLFW.GLFW_RELEASE), anyInt()); - verify(mouseHandler, atLeastOnce()) + verify(fixture.getMouseHandler(), atLeastOnce()) .onPress(eq(WINDOW_HANDLE), eq(1), eq(GLFW.GLFW_RELEASE), anyInt()); // Verify state is cleared @@ -335,7 +267,7 @@ void reset_withPressedKeys_releasesAllKeys() { @Test void getPressedKeys_afterKeyPress_returnsPressedKeys() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); RawInput input = new RawInput( @@ -354,16 +286,9 @@ void getPressedKeys_afterKeyPress_returnsPressedKeys() { @Test void inject_withTextInput_whenScreenOpen_typesCharacters() throws Exception { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); - // Set screen field using reflection - try { - Field screenField = Minecraft.class.getField("screen"); - screenField.setAccessible(true); - screenField.set(mc, screen); - } catch (Exception e) { - // If field can't be set, skip this test - return; - } + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); + // Set screen field using fixture helper + fixture.setMinecraftField("screen", fixture.getScreen()); String text = "Hello"; RawInput input = new RawInput(new int[] {}, 0.0f, 0.0f, (byte) 0, 0.0f, text); @@ -373,7 +298,7 @@ void inject_withTextInput_whenScreenOpen_typesCharacters() throws Exception { ArgumentCaptor charCaptor = ArgumentCaptor.forClass(Character.class); // Verify each character was typed - verify(screen, times(text.length())).charTyped(charCaptor.capture(), eq(0)); + verify(fixture.getScreen(), times(text.length())).charTyped(charCaptor.capture(), eq(0)); var capturedChars = charCaptor.getAllValues(); assertEquals('H', capturedChars.get(0)); @@ -387,16 +312,9 @@ void inject_withTextInput_whenScreenOpen_typesCharacters() throws Exception { @Test void inject_withTextInput_whenNoScreen_ignoresText() throws Exception { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); - // Set screen field to null using reflection - try { - Field screenField = Minecraft.class.getField("screen"); - screenField.setAccessible(true); - screenField.set(mc, null); - } catch (Exception e) { - // If field can't be set, skip this test - return; - } + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); + // Set screen field to null using fixture helper + fixture.setMinecraftField("screen", null); String text = "Hello"; RawInput input = new RawInput(new int[] {}, 0.0f, 0.0f, (byte) 0, 0.0f, text); @@ -404,7 +322,7 @@ void inject_withTextInput_whenNoScreen_ignoresText() throws Exception { injector.inject(input); // Should not call charTyped when no screen is open - verify(screen, never()).charTyped(anyChar(), anyInt()); + verify(fixture.getScreen(), never()).charTyped(anyChar(), anyInt()); } } @@ -423,21 +341,7 @@ void inject_whenMinecraftNotInitialized_doesNotCrash() { @Test void maintainButtonState_withHeldButton_firesPressEvents() { try (MockedStatic mockedMinecraft = mockStatic(Minecraft.class)) { - mockedMinecraft.when(Minecraft::getInstance).thenReturn(mc); - - // Ensure all fields are set up properly - try { - Field mouseHandlerField = Minecraft.class.getField("mouseHandler"); - mouseHandlerField.setAccessible(true); - mouseHandlerField.set(mc, mouseHandler); - } catch (Exception e) { - // If field can't be set, skip this test - return; - } - - // Make sure window is set up - when(mc.getWindow()).thenReturn(window); - when(window.getWindow()).thenReturn(WINDOW_HANDLE); + mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); // Inject with left mouse button held byte leftButton = 0b00000001; @@ -445,13 +349,13 @@ void maintainButtonState_withHeldButton_firesPressEvents() { injector.inject(input); // Reset the mock to clear previous invocations - reset(mouseHandler); + reset(fixture.getMouseHandler()); // Call maintainButtonState - this should not throw assertDoesNotThrow(() -> injector.maintainButtonState()); // Verify mouse button press event was fired by maintainButtonState - verify(mouseHandler, atLeastOnce()) + verify(fixture.getMouseHandler(), atLeastOnce()) .onPress(eq(WINDOW_HANDLE), eq(0), eq(GLFW.GLFW_PRESS), anyInt()); } } diff --git a/forge/src/test/java/com/mineagent/MinecraftTestFixture.java b/forge/src/test/java/com/mineagent/MinecraftTestFixture.java new file mode 100644 index 0000000..ab8ceb8 --- /dev/null +++ b/forge/src/test/java/com/mineagent/MinecraftTestFixture.java @@ -0,0 +1,162 @@ +package com.mineagent; + +import com.mojang.blaze3d.platform.Window; +import net.minecraft.client.KeyboardHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.MouseHandler; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Options; +import net.minecraft.client.gui.screens.Screen; +import java.lang.reflect.Field; +import static org.mockito.Mockito.*; +import org.mockito.quality.Strictness; + +/** + * Test fixture for setting up Minecraft mocks with all necessary fields configured. + * This handles the reflection needed to set public fields on Minecraft and Options classes. + * + * Usage: + *
+ * MinecraftTestFixture fixture = new MinecraftTestFixture();
+ * Minecraft mc = fixture.getMinecraft();
+ * // Use mc in your tests
+ * 
+ */ +public class MinecraftTestFixture { + private final Minecraft mc; + private final Window window; + private final KeyboardHandler keyboardHandler; + private final MouseHandler mouseHandler; + private final Options options; + private final KeyMapping keyAttack; + private final KeyMapping keyUse; + private final Screen screen; + + private static final long DEFAULT_WINDOW_HANDLE = 12345L; + + /** + * Creates a new test fixture with all mocks configured. + * The window handle defaults to 12345L. + */ + public MinecraftTestFixture() { + this(DEFAULT_WINDOW_HANDLE); + } + + /** + * Creates a new test fixture with a custom window handle. + * + * @param windowHandle The GLFW window handle to use + */ + public MinecraftTestFixture(long windowHandle) { + // Create all mocks with lenient settings + this.mc = mock(Minecraft.class, withSettings().strictness(Strictness.LENIENT)); + this.window = mock(Window.class, withSettings().strictness(Strictness.LENIENT)); + this.keyboardHandler = mock(KeyboardHandler.class, withSettings().strictness(Strictness.LENIENT)); + this.mouseHandler = mock(MouseHandler.class, withSettings().strictness(Strictness.LENIENT)); + this.options = mock(Options.class, withSettings().strictness(Strictness.LENIENT)); + this.keyAttack = mock(KeyMapping.class, withSettings().strictness(Strictness.LENIENT)); + this.keyUse = mock(KeyMapping.class, withSettings().strictness(Strictness.LENIENT)); + this.screen = mock(Screen.class, withSettings().strictness(Strictness.LENIENT)); + + // Setup common mock behavior + when(window.getWindow()).thenReturn(windowHandle); + when(mc.getWindow()).thenReturn(window); + + // Set fields using reflection + setupMinecraftFields(); + setupOptionsFields(); + } + + /** + * Sets up all Minecraft class fields using reflection. + */ + private void setupMinecraftFields() { + setField(Minecraft.class, mc, "keyboardHandler", keyboardHandler); + setField(Minecraft.class, mc, "mouseHandler", mouseHandler); + setField(Minecraft.class, mc, "options", options); + } + + /** + * Sets up all Options class fields using reflection. + */ + private void setupOptionsFields() { + setField(Options.class, options, "keyAttack", keyAttack); + setField(Options.class, options, "keyUse", keyUse); + } + + /** + * Sets a field on an object using reflection. + * Silently fails if the field doesn't exist or can't be accessed. + * + * @param clazz The class containing the field + * @param target The object instance to set the field on + * @param fieldName The name of the field + * @param value The value to set + */ + private static void setField(Class clazz, Object target, String fieldName, Object value) { + try { + Field field = clazz.getField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Field doesn't exist or can't be accessed - this might happen if + // Minecraft structure changes, but we continue anyway + // Tests will fail if they actually need this field + } + } + + /** + * Sets a field on the Minecraft instance. + * Useful for setting fields like "player" or "screen" in specific tests. + * + * @param fieldName The name of the field + * @param value The value to set + */ + public void setMinecraftField(String fieldName, Object value) { + setField(Minecraft.class, mc, fieldName, value); + } + + /** + * Sets a field on the Options instance. + * + * @param fieldName The name of the field + * @param value The value to set + */ + public void setOptionsField(String fieldName, Object value) { + setField(Options.class, options, fieldName, value); + } + + // Getters for all mocked components + + public Minecraft getMinecraft() { + return mc; + } + + public Window getWindow() { + return window; + } + + public KeyboardHandler getKeyboardHandler() { + return keyboardHandler; + } + + public MouseHandler getMouseHandler() { + return mouseHandler; + } + + public Options getOptions() { + return options; + } + + public KeyMapping getKeyAttack() { + return keyAttack; + } + + public KeyMapping getKeyUse() { + return keyUse; + } + + public Screen getScreen() { + return screen; + } +} From 665b99a0e82f6c0753437e74f70be4ff567cd41c Mon Sep 17 00:00:00 2001 From: thomashopkins32 Date: Sun, 25 Jan 2026 16:40:33 -0500 Subject: [PATCH 18/21] Update dependencies to fix pyright --- pixi.lock | 3169 ++++++++++++++++++++++++++---------------------- pyproject.toml | 7 +- 2 files changed, 1715 insertions(+), 1461 deletions(-) diff --git a/pixi.lock b/pixi.lock index 25ddae4..dc6d752 100644 --- a/pixi.lock +++ b/pixi.lock @@ -8,176 +8,191 @@ environments: - https://pypi.org/simple packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.9-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.15.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/editables-0.5-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h59595ed_1003.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py312hacea422_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.2.1-h3beb420_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.27.0-pypyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312hcaba1f9_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.74.1-py312h6f3464c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.0-h6083320_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.28.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h4637d8d_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.23-h4ddbbb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_105.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libde265-1.0.15-h00ab1b0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hf1ad2bd_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.84.1-h3618099_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.68.2-h25350d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.0.0-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hd9ff511_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.6.0-cpu_mkl_hc5f969b_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.3-h6548e54_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.74.1-h3288cfb_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libheif-1.19.7-gpl_hc18d805_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.2-default_hafda6a7_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.54-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.2-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.9.1-cpu_mkl_hfee2a32_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.6-h8d12d68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-hca6bf5a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-he237659_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.7-h024ca30_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-21.1.8-h4922eb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.6-h53dfc1b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py312h68727a3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py312h80c1187_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.0-h29eaf8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py312h2ec8cdc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.9-h5755bd7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.18.0-py312hd9148b4_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.0-py312h50c33e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-6.31.1-py312hb8af0ac_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.9-h9e4cc4f_1_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py312_h446997d_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312hda17c39_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py312_h11dcf35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.1-pyh7a1b43c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-3.0.1-pyhc7ab6ef_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.12-hd63d673_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.9.1-cpu_mkl_py312_h117a9d7_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.0-py312h54fa4ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.10.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-hb700be7_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.20.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312h4eba8b5_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.25.0-cpu_py312_hd7553a8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-extra-decoders-0.0.2-py312h49ceefc_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2026.1.14.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/x265-3.5-h924138e_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.4-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-hceb46e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz - - pypi: https://files.pythonhosted.org/packages/03/33/7a7388b9ef94aab40539939d94461ec682afbd895458945ed25be07f03f6/attrs-25.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/b8/a88b8d94e4aa048057e394140514158390fd485c9cc5686072688d4b5385/daemoniker-0.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/ef/09b53c076943c3722bac91a274825ece90985c1b3686d8426608fd741e30/gym-0.22.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/25/26/d786c6bec30fe6110fd3d22c9a273a2a0e56c0b73b93e25ea1af5a53243b/gym_notices-0.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/55/55d157aa8693090954fc9639bf27218240517c3bc7afa6e97412da6ebfd9/gym_notices-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/e8/facde510585869b5ec694e8e0363ffe4eba067cb357a8398a55f6a1f8023/importlib_resources-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/f8/309546aec092434166a6e11c7dcecb5c2d0a787c18c072d61e18da9eba57/lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: git+https://github.com/thomashopkins32/MineDojo.git?rev=main#03ed9b47aeb1e681d9650982f3a06794530781db - - pypi: https://files.pythonhosted.org/packages/a4/69/d3f343a61a2f86ef10ed7865a26beda7c71554136ce187b0384b1c2c9ca3/multiprocess-0.70.17-py312-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/e3/8c4e0d24b46fbf02e6b2dc2da5d18f0c73cfd343a1fb01ae64c788c20e56/Pyro4-4.82-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ad/86/e029bd8a451f145e28530128239be057ca6e701aac46ad0d0000f852d551/serpent-1.41-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/24/73031e6bd25d8f94811b3752b0b217efbdb20a67b65c6838c9af4e50c2e2/serpent-1.42-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip - - pypi: https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl - pypi: ./ dev: channels: @@ -187,194 +202,214 @@ environments: - https://pypi.org/simple packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.9-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.15.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/editables-0.5-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h59595ed_1003.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py312hacea422_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.2.1-h3beb420_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.27.0-pypyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312hcaba1f9_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.74.1-py312h6f3464c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.0-h6083320_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.28.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h4637d8d_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.23-h4ddbbb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_105.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libde265-1.0.15-h00ab1b0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hf1ad2bd_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.84.1-h3618099_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.68.2-h25350d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.0.0-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hd9ff511_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.6.0-cpu_mkl_hc5f969b_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.3-h6548e54_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.74.1-h3288cfb_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libheif-1.19.7-gpl_hc18d805_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.2-default_hafda6a7_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.54-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.2-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.9.1-cpu_mkl_hfee2a32_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.6-h8d12d68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-hca6bf5a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-he237659_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.7-h024ca30_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-21.1.8-h4922eb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.2.1-h728ac57_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.6-h53dfc1b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py312h68727a3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py312h80c1187_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.0-h29eaf8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py312h2ec8cdc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.9-h5755bd7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.18.0-py312hd9148b4_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.0-py312h50c33e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-6.31.1-py312hb8af0ac_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.9-h9e4cc4f_1_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py312_h446997d_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312hda17c39_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py312_h11dcf35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.1-pyh7a1b43c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-3.0.1-pyhc7ab6ef_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.12-hd63d673_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.9.1-cpu_mkl_py312_h117a9d7_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.0-py312h54fa4ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.10.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-hb700be7_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.20.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312h4eba8b5_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.25.0-cpu_py312_hd7553a8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-extra-decoders-0.0.2-py312h49ceefc_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2026.1.14.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/x265-3.5-h924138e_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.4-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-hceb46e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz - - pypi: https://files.pythonhosted.org/packages/03/33/7a7388b9ef94aab40539939d94461ec682afbd895458945ed25be07f03f6/attrs-25.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/b8/a88b8d94e4aa048057e394140514158390fd485c9cc5686072688d4b5385/daemoniker-0.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c1/d7/3c87cf147185d91c2e946460a5cf68c236427b4a23ab96793ccb7d8017c9/fonttools-4.58.5-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/01/ef/09b53c076943c3722bac91a274825ece90985c1b3686d8426608fd741e30/gym-0.22.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/25/26/d786c6bec30fe6110fd3d22c9a273a2a0e56c0b73b93e25ea1af5a53243b/gym_notices-0.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/55/55d157aa8693090954fc9639bf27218240517c3bc7afa6e97412da6ebfd9/gym_notices-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/e8/facde510585869b5ec694e8e0363ffe4eba067cb357a8398a55f6a1f8023/importlib_resources-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c5/f8/309546aec092434166a6e11c7dcecb5c2d0a787c18c072d61e18da9eba57/lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: git+https://github.com/thomashopkins32/MineDojo.git?rev=main#03ed9b47aeb1e681d9650982f3a06794530781db - - pypi: https://files.pythonhosted.org/packages/a4/69/d3f343a61a2f86ef10ed7865a26beda7c71554136ce187b0384b1c2c9ca3/multiprocess-0.70.17-py312-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/80/be/ecb7cfb42d242b7ee764b52e6ff4782beeec00e3b943a3ec832b281f9da6/pyright-1.1.396-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/e3/8c4e0d24b46fbf02e6b2dc2da5d18f0c73cfd343a1fb01ae64c788c20e56/Pyro4-4.82-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/ad/86/e029bd8a451f145e28530128239be057ca6e701aac46ad0d0000f852d551/serpent-1.41-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/07/24/73031e6bd25d8f94811b3752b0b217efbdb20a67b65c6838c9af4e50c2e2/serpent-1.42-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c2/eb/c6db6e3001d58c6a9e67c74bb7b4206767caa3ccc28c6b9eaf4c23fb4e34/virtualenv-20.29.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip - - pypi: https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl - pypi: ./ py312: channels: @@ -384,383 +419,325 @@ environments: - https://pypi.org/simple packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.2.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.9-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.15.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/editables-0.5-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h59595ed_1003.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py312hacea422_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.0.0-h76408a6_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.27.0-pypyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312hcaba1f9_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.74.1-py312h6f3464c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.0-h6083320_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.28.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h4637d8d_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.23-h4ddbbb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_105.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libde265-1.0.15-h00ab1b0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hf1ad2bd_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.84.1-h3618099_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.68.2-h25350d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.0.0-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hd9ff511_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.6.0-cpu_mkl_hc5f969b_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.3-h6548e54_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.74.1-h3288cfb_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libheif-1.19.7-gpl_hc18d805_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.2-default_hafda6a7_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.54-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.2-hf4e2dac_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.9.1-cpu_mkl_hfee2a32_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.6-h8d12d68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-hca6bf5a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-he237659_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.7-h024ca30_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-21.1.8-h4922eb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.6-h53dfc1b_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py312h68727a3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py312h80c1187_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.0-h29eaf8c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py312h2ec8cdc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.9-h5755bd7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.18.0-py312hd9148b4_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.0-py312h50c33e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-6.31.1-py312hb8af0ac_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.9-h9e4cc4f_1_cpython.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py312_h446997d_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312hda17c39_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py312_h11dcf35_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.1-pyh7a1b43c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-3.0.1-pyhc7ab6ef_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.12-hd63d673_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.9.1-cpu_mkl_py312_h117a9d7_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.0-py312h54fa4ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.10.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-hb700be7_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.20.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312h4eba8b5_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.25.0-cpu_py312_hd7553a8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-extra-decoders-0.0.2-py312h49ceefc_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2026.1.14.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/x265-3.5-h924138e_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.4-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxt-1.3.1-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-hceb46e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz - - pypi: https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/b8/a88b8d94e4aa048057e394140514158390fd485c9cc5686072688d4b5385/daemoniker-0.2.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/ef/09b53c076943c3722bac91a274825ece90985c1b3686d8426608fd741e30/gym-0.22.0.tar.gz - - pypi: https://files.pythonhosted.org/packages/25/26/d786c6bec30fe6110fd3d22c9a273a2a0e56c0b73b93e25ea1af5a53243b/gym_notices-0.0.8-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/55/55d157aa8693090954fc9639bf27218240517c3bc7afa6e97412da6ebfd9/gym_notices-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/93/e8/facde510585869b5ec694e8e0363ffe4eba067cb357a8398a55f6a1f8023/importlib_resources-6.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/f8/309546aec092434166a6e11c7dcecb5c2d0a787c18c072d61e18da9eba57/lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: git+https://github.com/thomashopkins32/MineDojo.git?rev=main#03ed9b47aeb1e681d9650982f3a06794530781db - - pypi: https://files.pythonhosted.org/packages/a4/69/d3f343a61a2f86ef10ed7865a26beda7c71554136ce187b0384b1c2c9ca3/multiprocess-0.70.17-py312-none-any.whl - - pypi: https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/e3/8c4e0d24b46fbf02e6b2dc2da5d18f0c73cfd343a1fb01ae64c788c20e56/Pyro4-4.82-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ad/86/e029bd8a451f145e28530128239be057ca6e701aac46ad0d0000f852d551/serpent-1.41-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/07/24/73031e6bd25d8f94811b3752b0b217efbdb20a67b65c6838c9af4e50c2e2/serpent-1.42-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip - - pypi: https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl - pypi: ./ packages: -- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - build_number: 3 - sha256: cec7343e76c9da6a42c7e7cba53391daa6b46155054ef61a5ef522ea27c5a058 - md5: ee5c2118262e30b972bc0b4db8ef0ba5 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: c0cddb66070dd6355311f7667ce2acccf70d1013edaa6e97f22859502fefdb22 + md5: 887b70e1d607fba7957aa02f9ee0d939 depends: - llvm-openmp >=9.0.1 license: BSD-3-Clause license_family: BSD purls: [] - size: 7649 - timestamp: 1741390353130 -- conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.2.0-pyhd8ed1ab_0.conda - sha256: fc8d8b30cd573fcfd77f696b7194cc04ab4d9299780f9d3973720ab8378bfcd0 - md5: 107112a92cba7895f10a4c246c1770a1 + size: 8244 + timestamp: 1764092331208 +- conda: https://conda.anaconda.org/conda-forge/noarch/absl-py-2.3.1-pyhd8ed1ab_0.conda + sha256: ec7a804be25350c310be7e0fffdbf4006fd22a650bf316513bdd71cb922944bf + md5: 7d4f1ddc43d323c916b2c744835eb093 depends: - python >=3.9 license: Apache-2.0 license_family: Apache purls: - pkg:pypi/absl-py?source=hash-mapping - size: 198531 - timestamp: 1742562522692 -- conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.14-hb9d3cd8_0.conda - sha256: b9214bc17e89bf2b691fad50d952b7f029f6148f4ac4fe7c60c08f093efdf745 - md5: 76df83c2a9035c54df5d04ff81bcc02d + size: 109408 + timestamp: 1751547635237 +- conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.15.3-hb03c661_0.conda + sha256: d88aa7ae766cf584e180996e92fef2aa7d8e0a0a5ab1d4d49c32390c1b5fff31 + md5: dcdc58c15961dbf17a0621312b01f5cb depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: LGPL-2.1-or-later license_family: GPL purls: [] - size: 566531 - timestamp: 1744668655747 + size: 584660 + timestamp: 1768327524772 - pypi: https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz name: antlr4-python3-runtime version: 4.9.3 sha256: f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b requires_dist: - typing ; python_full_version < '3.5' -- pypi: https://files.pythonhosted.org/packages/03/33/7a7388b9ef94aab40539939d94461ec682afbd895458945ed25be07f03f6/attrs-25.2.0-py3-none-any.whl - name: attrs - version: 25.2.0 - sha256: 611344ff0a5fed735d86d7784610c84f8126b95e549bcad9ff61b4242f2d386b - requires_dist: - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'benchmark' - - hypothesis ; extra == 'benchmark' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'benchmark' - - pympler ; extra == 'benchmark' - - pytest-codspeed ; extra == 'benchmark' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'benchmark' - - pytest-xdist[psutil] ; extra == 'benchmark' - - pytest>=4.3.0 ; extra == 'benchmark' - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'cov' - - coverage[toml]>=5.3 ; extra == 'cov' - - hypothesis ; extra == 'cov' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'cov' - - pympler ; extra == 'cov' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'cov' - - pytest-xdist[psutil] ; extra == 'cov' - - pytest>=4.3.0 ; extra == 'cov' - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'dev' - - hypothesis ; extra == 'dev' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'dev' - - pre-commit-uv ; extra == 'dev' - - pympler ; extra == 'dev' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'dev' - - pytest-xdist[psutil] ; extra == 'dev' - - pytest>=4.3.0 ; extra == 'dev' - - cogapp ; extra == 'docs' - - furo ; extra == 'docs' - - myst-parser ; extra == 'docs' - - sphinx ; extra == 'docs' - - sphinx-notfound-page ; extra == 'docs' - - sphinxcontrib-towncrier ; extra == 'docs' - - towncrier ; extra == 'docs' - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'tests' - - hypothesis ; extra == 'tests' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests' - - pympler ; extra == 'tests' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests' - - pytest-xdist[psutil] ; extra == 'tests' - - pytest>=4.3.0 ; extra == 'tests' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests-mypy' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests-mypy' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl +- conda: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda + sha256: b08ef033817b5f9f76ce62dfcac7694e7b6b4006420372de22494503decac855 + md5: 346722a0be40f6edc53f12640d301338 + depends: + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 2706396 + timestamp: 1718551242397 +- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl name: attrs - version: 25.3.0 - sha256: 427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 - requires_dist: - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'benchmark' - - hypothesis ; extra == 'benchmark' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'benchmark' - - pympler ; extra == 'benchmark' - - pytest-codspeed ; extra == 'benchmark' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'benchmark' - - pytest-xdist[psutil] ; extra == 'benchmark' - - pytest>=4.3.0 ; extra == 'benchmark' - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'cov' - - coverage[toml]>=5.3 ; extra == 'cov' - - hypothesis ; extra == 'cov' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'cov' - - pympler ; extra == 'cov' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'cov' - - pytest-xdist[psutil] ; extra == 'cov' - - pytest>=4.3.0 ; extra == 'cov' - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'dev' - - hypothesis ; extra == 'dev' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'dev' - - pre-commit-uv ; extra == 'dev' - - pympler ; extra == 'dev' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'dev' - - pytest-xdist[psutil] ; extra == 'dev' - - pytest>=4.3.0 ; extra == 'dev' - - cogapp ; extra == 'docs' - - furo ; extra == 'docs' - - myst-parser ; extra == 'docs' - - sphinx ; extra == 'docs' - - sphinx-notfound-page ; extra == 'docs' - - sphinxcontrib-towncrier ; extra == 'docs' - - towncrier ; extra == 'docs' - - cloudpickle ; platform_python_implementation == 'CPython' and extra == 'tests' - - hypothesis ; extra == 'tests' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests' - - pympler ; extra == 'tests' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests' - - pytest-xdist[psutil] ; extra == 'tests' - - pytest>=4.3.0 ; extra == 'tests' - - mypy>=1.11.1 ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests-mypy' - - pytest-mypy-plugins ; python_full_version >= '3.10' and platform_python_implementation == 'CPython' and extra == 'tests-mypy' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d - md5: 62ee74e96c5ebb0af99386de58cf9553 + version: 25.4.0 + sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda + sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 + md5: 51a19bba1b8ebfb60df25cde030b7ebc depends: - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 + - libgcc >=14 license: bzip2-1.0.6 license_family: BSD purls: [] - size: 252783 - timestamp: 1720974456583 -- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda - sha256: f8003bef369f57396593ccd03d08a8e21966157269426f71e943f96e4b579aeb - md5: f7f0d6cc2dc986d42ac2689ec88192be + size: 260341 + timestamp: 1757437258798 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e + md5: 920bb03579f15389b9e512095ad995b7 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 206884 - timestamp: 1744127994291 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - sha256: bf832198976d559ab44d6cdb315642655547e26d826e34da67cbee6624cda189 - md5: 19f3a56f68d2fd06c516076bff482c52 + size: 207882 + timestamp: 1765214722852 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda + sha256: b5974ec9b50e3c514a382335efa81ed02b05906849827a34061c496f4defa0b2 + md5: bddacf101bb4dd0e51811cb69c7790e2 + depends: + - __unix license: ISC purls: [] - size: 158144 - timestamp: 1738298224464 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - sha256: 3bd6a391ad60e471de76c0e9db34986c4b5058587fbf2efa5a7f54645e28c2c7 - md5: 09262e66b19567aff4f592fb53b28760 + size: 146519 + timestamp: 1767500828366 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + sha256: 06525fa0c4e4f56e771a3b986d0fdf0f0fc5a3270830ee47e127a5105bde1b9a + md5: bb6c4808bfa69d6f7f6b07e5846ced37 depends: - __glibc >=2.17,<3.0.a0 - fontconfig >=2.15.0,<3.0a0 - fonts-conda-ecosystem - - freetype >=2.12.1,<3.0a0 - - icu >=75.1,<76.0a0 - - libexpat >=2.6.4,<3.0a0 - - libgcc >=13 - - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.47,<1.7.0a0 - - libstdcxx >=13 + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libstdcxx >=14 - libxcb >=1.17.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - pixman >=0.44.2,<1.0a0 + - pixman >=0.46.4,<1.0a0 - xorg-libice >=1.1.2,<2.0a0 - - xorg-libsm >=1.2.5,<2.0a0 - - xorg-libx11 >=1.8.11,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - xorg-libx11 >=1.8.12,<2.0a0 - xorg-libxext >=1.3.6,<2.0a0 - xorg-libxrender >=0.9.12,<0.10.0a0 license: LGPL-2.1-only or MPL-1.1 purls: [] - size: 978114 - timestamp: 1741554591855 -- pypi: https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl + size: 989514 + timestamp: 1766415934926 +- pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl name: certifi - version: 2025.1.31 - sha256: ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe - requires_python: '>=3.6' -- pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl + version: 2026.1.4 + sha256: 9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl name: cfgv - version: 3.4.0 - sha256: b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + version: 3.5.0 + sha256: a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: charset-normalizer - version: 3.4.1 - sha256: bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d + version: 3.4.4 + sha256: 11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86 requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl name: cloudpickle - version: 3.1.1 - sha256: c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e + version: 3.1.2 + sha256: 9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl name: coloredlogs @@ -770,12 +747,12 @@ packages: - humanfriendly>=9.1 - capturer>=2.4 ; extra == 'cron' requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*' -- pypi: https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: contourpy - version: 1.3.2 - sha256: f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe + version: 1.3.3 + sha256: 4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1 requires_dist: - - numpy>=1.23 + - numpy>=1.25 - furo ; extra == 'docs' - sphinx>=7.2 ; extra == 'docs' - sphinx-copybutton ; extra == 'docs' @@ -784,7 +761,7 @@ packages: - contourpy[bokeh,docs] ; extra == 'mypy' - bokeh ; extra == 'mypy' - docutils-stubs ; extra == 'mypy' - - mypy==1.15.0 ; extra == 'mypy' + - mypy==1.17.0 ; extra == 'mypy' - types-pillow ; extra == 'mypy' - contourpy[test-no-images] ; extra == 'test' - matplotlib ; extra == 'test' @@ -794,18 +771,18 @@ packages: - pytest-rerunfailures ; extra == 'test-no-images' - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' - requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.9-py312hd8ed1ab_1.conda + requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.12-py312hd8ed1ab_1.conda noarch: generic - sha256: 58a637bc8328b115c9619de3fcd664ec26662083319e3c106917a1b3ee4d7594 - md5: f0f8087079679f3ae375fca13327b17f + sha256: b88c76a6d6b45378552ccfd9e88b2a073161fe83fd1294c8fa103ffd32f7934a + md5: 99d689ccc1a360639eec979fd7805be9 depends: - - python 3.12.9.* + - python >=3.12,<3.13.0a0 - python_abi * *_cp312 license: Python-2.0 purls: [] - size: 45728 - timestamp: 1741128060593 + size: 45767 + timestamp: 1761175217281 - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl name: cycler version: 0.12.1 @@ -840,18 +817,28 @@ packages: sha256: 72df5fb977b7951a1a48039dad349a06d9326e700f032f2e0114ad9643b93e98 requires_dist: - psutil ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/46/d1/e73b6ad76f0b1fb7f23c35c6d95dbc506a9c8804f43dda8cb5b0fa6331fd/dill-0.3.9-py3-none-any.whl +- conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda + sha256: 22053a5842ca8ee1cf8e1a817138cdb5e647eb2c46979f84153f6ad7bde73020 + md5: 418c6ca5929a611cbd69204907a83995 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 760229 + timestamp: 1685695754230 +- pypi: https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl name: dill - version: 0.3.9 - sha256: 468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a + version: 0.4.1 + sha256: 1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d requires_dist: - objgraph>=1.7.2 ; extra == 'graph' - gprof2dot>=2022.7.29 ; extra == 'profile' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl name: distlib - version: 0.3.9 - sha256: 47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 + version: 0.4.0 + sha256: 9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 - conda: https://conda.anaconda.org/conda-forge/noarch/editables-0.5-pyhd8ed1ab_1.conda sha256: 8d4f908e670be360617d418c328213bc46e7100154c3742db085148141712f60 md5: 2cf824fe702d88e641eec9f9f653e170 @@ -867,16 +854,28 @@ packages: name: farama-notifications version: 0.0.4 sha256: 14de931035a41961f7c056361dc7f980762a143d05791ef5794a751a2caf05ae -- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda - sha256: 006d7e5a0c17a6973596dd86bfc80d74ce541144d2aee2d22d46fd41df560a63 - md5: 7f402b4a1007ee355bc50ce4d24d4a57 +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.20.3-pyhd8ed1ab_0.conda + sha256: 8b90dc21f00167a7e58abb5141a140bdb31a7c5734fe1361b5f98f4a4183fd32 + md5: 2cfaaccf085c133a477f0a7a8657afe9 depends: - - python >=3.9 + - python >=3.10 license: Unlicense purls: - pkg:pypi/filelock?source=hash-mapping - size: 17544 - timestamp: 1737517924333 + size: 18661 + timestamp: 1768022315929 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-hff5e90c_0.conda + sha256: d4e92ba7a7b4965341dc0fca57ec72d01d111b53c12d11396473115585a9ead6 + md5: f7d7a4104082b39e3b3473fbd4a38229 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + size: 198107 + timestamp: 1767681153946 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b md5: 0c96522c6bdaed4b1566d11387caaf45 @@ -934,30 +933,29 @@ packages: purls: [] size: 3667 timestamp: 1566974674465 -- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - sha256: 53f23a3319466053818540bcdf2091f253cbdbab1e0e9ae7b9e509dcaa2a5e38 - md5: f766549260d6815b0c52253f1fb1bb29 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 + md5: a7970cd949a077b7cb9696379d338681 depends: - - font-ttf-dejavu-sans-mono + - font-ttf-ubuntu - font-ttf-inconsolata + - font-ttf-dejavu-sans-mono - font-ttf-source-code-pro - - font-ttf-ubuntu license: BSD-3-Clause license_family: BSD purls: [] - size: 4102 - timestamp: 1566932280397 -- pypi: https://files.pythonhosted.org/packages/c1/d7/3c87cf147185d91c2e946460a5cf68c236427b4a23ab96793ccb7d8017c9/fonttools-4.58.5-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl + size: 4059 + timestamp: 1762351264405 +- pypi: https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl name: fonttools - version: 4.58.5 - sha256: 2af65836cf84cd7cb882d0b353bdc73643a497ce23b7414c26499bb8128ca1af + version: 4.61.1 + sha256: 10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796 requires_dist: - - fs>=2.2.0,<3 ; extra == 'ufo' - lxml>=4.0 ; extra == 'lxml' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' - zopfli>=0.1.4 ; extra == 'woff' - - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'unicode' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'unicode' - lz4>=1.7.4.2 ; extra == 'graphite' - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' @@ -966,13 +964,12 @@ packages: - sympy ; extra == 'symfont' - xattr ; sys_platform == 'darwin' and extra == 'type1' - skia-pathops>=0.5.0 ; extra == 'pathops' - - uharfbuzz>=0.23.0 ; extra == 'repacker' - - fs>=2.2.0,<3 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'repacker' - lxml>=4.0 ; extra == 'all' - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' - zopfli>=0.1.4 ; extra == 'all' - - unicodedata2>=15.1.0 ; python_full_version < '3.13' and extra == 'all' + - unicodedata2>=17.0.0 ; python_full_version < '3.15' and extra == 'all' - lz4>=1.7.4.2 ; extra == 'all' - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' @@ -981,29 +978,29 @@ packages: - sympy ; extra == 'all' - xattr ; sys_platform == 'darwin' and extra == 'all' - skia-pathops>=0.5.0 ; extra == 'all' - - uharfbuzz>=0.23.0 ; extra == 'all' - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.13.3-ha770c72_1.conda - sha256: 7ef7d477c43c12a5b4cddcf048a83277414512d1116aba62ebadfa7056a7d84f - md5: 9ccd736d31e0c6e41f54e704e5312811 + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.1-ha770c72_0.conda + sha256: bf8e4dffe46f7d25dc06f31038cacb01672c47b9f45201f065b0f4d00ab0a83e + md5: 4afc585cd97ba8a23809406cd8a9eda8 depends: - - libfreetype 2.13.3 ha770c72_1 - - libfreetype6 2.13.3 h48d6fc4_1 + - libfreetype 2.14.1 ha770c72_0 + - libfreetype6 2.14.1 h73754d4_0 license: GPL-2.0-only OR FTL purls: [] - size: 172450 - timestamp: 1745369996765 -- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.0-pyhd8ed1ab_0.conda - sha256: 9cbba3b36d1e91e4806ba15141936872d44d20a4d1e3bb74f4aea0ebeb01b205 - md5: 5ecafd654e33d1f2ecac5ec97057593b + size: 173114 + timestamp: 1757945422243 +- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.1.0-pyhd8ed1ab_0.conda + sha256: bfba6c280366f48b00a6a7036988fc2bc3fea5ac1d8303152c9da69d72a22936 + md5: 1daaf94a304a27ba3446a306235a37ea depends: - - python >=3.9 + - python >=3.10 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/fsspec?source=hash-mapping - size: 141329 - timestamp: 1741404114588 + - pkg:pypi/fsspec?source=compressed-mapping + size: 148116 + timestamp: 1768000866082 - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda sha256: aac402a8298f0c0cc528664249170372ef6b37ac39fdc92b40601a6aed1e32ff md5: 3bf7b9fd5a7136126e0234db4b87c8b6 @@ -1024,13 +1021,13 @@ packages: purls: [] size: 460055 timestamp: 1718980856608 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - sha256: addd0bc226ca86c11f1223ab322d12b67501c2b3d93749bdab2068ccaedd8ef0 - md5: 673ef4d6611f5b4ca7b5c1f8c65a38dc +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312hcaba1f9_2.conda + sha256: c9dfe9be6716d992b4ddd2e8219ad8afbe33dc04f69748ac4302954ba2e29e84 + md5: dd6f5de6249439ab97a6e9e873741294 depends: - __glibc >=2.17,<3.0.a0 - gmp >=6.3.0,<7.0a0 - - libgcc >=13 + - libgcc >=14 - mpc >=1.3.1,<2.0a0 - mpfr >=4.2.1,<5.0a0 - python >=3.12,<3.13.0a0 @@ -1039,38 +1036,39 @@ packages: license_family: LGPL purls: - pkg:pypi/gmpy2?source=hash-mapping - size: 209631 - timestamp: 1733462668219 -- conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - sha256: f915593b543c4156d0d9dce05e0056975482ae25b1637cb5830efaeb4bffb78a - md5: b0d211f827e1827cafe15b4000500223 + size: 214554 + timestamp: 1762946924209 +- conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda + sha256: 20dd623f094679d910110ce0ecbd9c5b1c8008b20ea0117b7656ee5e2c110f37 + md5: bc615ee587a58195f3618317ade7e8ca depends: - __unix - - openjdk >=8,<23 + - openjdk >=17,<25 license: Apache-2.0 license_family: APACHE purls: [] - size: 134432899 - timestamp: 1732134820391 -- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h59595ed_1003.conda - sha256: 0595b009f20f8f60f13a6398e7cdcbd2acea5f986633adcf85f5a2283c992add - md5: f87c7b7c2cb45f323ffbce941c78ab7c + size: 133208780 + timestamp: 1762592486483 +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda + sha256: 25ba37da5c39697a77fce2c9a15e48cf0a84f1464ad2aafbe53d8357a9f6cc8c + md5: 2cd94587f3a401ae05e03a6caf09539d depends: - - libgcc-ng >=12 - - libstdcxx-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 license: LGPL-2.0-or-later license_family: LGPL purls: [] - size: 96855 - timestamp: 1711634169756 -- conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.68.2-py312hacea422_0.conda - sha256: e245961c8682505ef719ab975503f085a00b1fac1bcb4eb3334919dd8a5e5ce1 - md5: 34ce5820aac57bc90820faf96f05ce63 + size: 99596 + timestamp: 1755102025473 +- conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.74.1-py312h6f3464c_1.conda + sha256: ae10c8231e5f8d680d8365ea2c249136ad18c0b49cd8480d9bde5655e408044a + md5: 9d1107a3026de28d5dfada8077d9681d depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libgrpc 1.68.2 h25350d4_0 - - libstdcxx >=13 + - libgcc >=14 + - libgrpc 1.74.1 h3288cfb_1 + - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 @@ -1078,8 +1076,8 @@ packages: license_family: APACHE purls: - pkg:pypi/grpcio?source=hash-mapping - size: 935095 - timestamp: 1740918841976 + size: 884426 + timestamp: 1759629887798 - pypi: https://files.pythonhosted.org/packages/01/ef/09b53c076943c3722bac91a274825ece90985c1b3686d8426608fd741e30/gym-0.22.0.tar.gz name: gym version: 0.22.0 @@ -1102,143 +1100,131 @@ packages: - lz4>=3.1.0 ; extra == 'nomujoco' - opencv-python>=3.0 ; extra == 'nomujoco' - pygame==2.1.0 ; extra == 'nomujoco' + - scipy>=1.4.1 ; extra == 'nomujoco' - box2d-py==2.3.5 ; extra == 'nomujoco' - pygame==2.1.0 ; extra == 'nomujoco' - pygame==2.1.0 ; extra == 'nomujoco' - - scipy>=1.4.1 ; extra == 'nomujoco' - - mujoco-py>=1.50,<2.0 ; extra == 'all' - lz4>=3.1.0 ; extra == 'all' - opencv-python>=3.0 ; extra == 'all' - - box2d-py==2.3.5 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' + - mujoco-py>=1.50,<2.0 ; extra == 'all' + - pygame==2.1.0 ; extra == 'all' + - scipy>=1.4.1 ; extra == 'all' + - box2d-py==2.3.5 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - ale-py~=0.7.4 ; extra == 'all' - lz4>=3.1.0 ; extra == 'all' - opencv-python>=3.0 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' + - scipy>=1.4.1 ; extra == 'all' - box2d-py==2.3.5 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - pygame==2.1.0 ; extra == 'all' - - scipy>=1.4.1 ; extra == 'all' - - pygame==2.1.0 ; extra == 'all' - - scipy>=1.4.1 ; extra == 'all' requires_python: '>=3.7' -- pypi: https://files.pythonhosted.org/packages/25/26/d786c6bec30fe6110fd3d22c9a273a2a0e56c0b73b93e25ea1af5a53243b/gym_notices-0.0.8-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/41/55/55d157aa8693090954fc9639bf27218240517c3bc7afa6e97412da6ebfd9/gym_notices-0.1.0-py3-none-any.whl name: gym-notices - version: 0.0.8 - sha256: e5f82e00823a166747b4c2a07de63b6560b1acb880638547e0cabf825a01e463 -- pypi: https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl + version: 0.1.0 + sha256: a943af4446cb619d04fd1e470b9272b4473e08a06d1c7cc9005755a4a0b8c905 +- pypi: https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl name: gymnasium - version: 1.1.1 - sha256: 9c167ec0a2b388666e37f63b2849cd2552f7f5b71938574c637bb36487eb928a + version: 1.2.3 + sha256: e6314bba8f549c7fdcc8677f7cd786b64908af6e79b57ddaa5ce1825bffb5373 requires_dist: - numpy>=1.21.0 - cloudpickle>=1.2.0 - - importlib-metadata>=4.8.0 ; python_full_version < '3.10' - typing-extensions>=4.3.0 - farama-notifications>=0.0.1 - ale-py>=0.9 ; extra == 'atari' - - box2d-py==2.3.5 ; extra == 'box2d' + - box2d==2.3.10 ; extra == 'box2d' - pygame>=2.1.3 ; extra == 'box2d' - swig==4.* ; extra == 'box2d' - pygame>=2.1.3 ; extra == 'classic-control' - pygame>=2.1.3 ; extra == 'classic-control' - - mujoco-py>=2.1,<2.2 ; extra == 'mujoco-py' - - cython<3 ; extra == 'mujoco-py' - - mujoco-py>=2.1,<2.2 ; extra == 'mujoco-py' - - cython<3 ; extra == 'mujoco-py' - mujoco>=2.1.5 ; extra == 'mujoco' - imageio>=2.14.1 ; extra == 'mujoco' + - packaging>=23.0 ; extra == 'mujoco' - pygame>=2.1.3 ; extra == 'toy-text' - pygame>=2.1.3 ; extra == 'toy-text' - jax>=0.4.16 ; extra == 'jax' - jaxlib>=0.4.16 ; extra == 'jax' - flax>=0.5.0 ; extra == 'jax' + - array-api-compat>=1.11.0 ; extra == 'jax' + - numpy>=2.1 ; extra == 'jax' - torch>=1.13.0 ; extra == 'torch' + - array-api-compat>=1.11.0 ; extra == 'torch' + - numpy>=2.1 ; extra == 'torch' + - array-api-compat>=1.11.0 ; extra == 'array-api' + - numpy>=2.1 ; extra == 'array-api' + - packaging>=23.0 ; extra == 'array-api' - moviepy>=1.0.0 ; extra == 'other' - matplotlib>=3.0 ; extra == 'other' - opencv-python>=3.0 ; extra == 'other' - seaborn>=0.13 ; extra == 'other' - ale-py>=0.9 ; extra == 'all' - - box2d-py==2.3.5 ; extra == 'all' + - box2d==2.3.10 ; extra == 'all' - pygame>=2.1.3 ; extra == 'all' - swig==4.* ; extra == 'all' - pygame>=2.1.3 ; extra == 'all' - - mujoco-py>=2.1,<2.2 ; extra == 'all' - - cython<3 ; extra == 'all' - mujoco>=2.1.5 ; extra == 'all' - imageio>=2.14.1 ; extra == 'all' + - packaging>=23.0 ; extra == 'all' - pygame>=2.1.3 ; extra == 'all' - jax>=0.4.16 ; extra == 'all' - jaxlib>=0.4.16 ; extra == 'all' - flax>=0.5.0 ; extra == 'all' + - array-api-compat>=1.11.0 ; extra == 'all' + - numpy>=2.1 ; extra == 'all' - torch>=1.13.0 ; extra == 'all' + - array-api-compat>=1.11.0 ; extra == 'all' + - numpy>=2.1 ; extra == 'all' + - array-api-compat>=1.11.0 ; extra == 'all' + - numpy>=2.1 ; extra == 'all' - opencv-python>=3.0 ; extra == 'all' - matplotlib>=3.0 ; extra == 'all' - moviepy>=1.0.0 ; extra == 'all' - pytest>=7.1.3 ; extra == 'testing' - scipy>=1.7.3 ; extra == 'testing' - dill>=0.3.7 ; extra == 'testing' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.0.0-h76408a6_0.conda - sha256: 9d33201d3e12a61d4ea4b1252a3468afb18b11a418f095dceffdf09bc6792f59 - md5: 347cb348bfc8d77062daee11c326e518 - depends: - - __glibc >=2.17,<3.0.a0 - - cairo >=1.18.4,<2.0a0 - - freetype >=2.13.3,<3.0a0 - - graphite2 - - icu >=75.1,<76.0a0 - - libexpat >=2.6.4,<3.0a0 - - libgcc >=13 - - libglib >=2.84.0,<3.0a0 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - license: MIT - license_family: MIT - purls: [] - size: 1720702 - timestamp: 1743082646624 -- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.2.1-h3beb420_0.conda - sha256: 5bd0f3674808862838d6e2efc0b3075e561c34309c5c2f4c976f7f1f57c91112 - md5: 0e6e192d4b3d95708ad192d957cf3163 + - array-api-extra>=0.7.0 ; extra == 'testing' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.0-h6083320_0.conda + sha256: eb0ff4632c76d5840ad8f509dc55694f79d9ac9bea5529944640e28e490361b0 + md5: 1ea5ed29aea252072b975a232b195146 depends: - __glibc >=2.17,<3.0.a0 - cairo >=1.18.4,<2.0a0 - - freetype - - graphite2 - - icu >=75.1,<76.0a0 - - libexpat >=2.7.0,<3.0a0 - - libfreetype >=2.13.3 - - libfreetype6 >=2.13.3 - - libgcc >=13 - - libglib >=2.84.1,<3.0a0 - - libstdcxx >=13 + - graphite2 >=1.3.14,<2.0a0 + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libglib >=2.86.3,<3.0a0 + - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT purls: [] - size: 1730226 - timestamp: 1747091044218 -- conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.27.0-pypyhd8ed1ab_0.conda - sha256: e83420f81390535774ac33b83d05249b8993e5376b76b4d461f83a77549e493d - md5: b85c18ba6e927ae0da3fde426c893cc8 + size: 2062122 + timestamp: 1766937132307 +- conda: https://conda.anaconda.org/conda-forge/noarch/hatchling-1.28.0-pyhcf101f3_1.conda + sha256: c83a28bacd1918e84261ba8bc3fe51689b93f5a2b9b31447a73d2090723b8442 + md5: a920c64f09ba92514c9c288bc91b783d depends: + - python >=3.10 - editables >=0.3 - - importlib-metadata - - packaging >=21.3 + - packaging >=24.2 - pathspec >=0.10.1 - pluggy >=1.0.0 - - python >=3.7 - - python >=3.8 - tomli >=1.2.2 - trove-classifiers + - python license: MIT license_family: MIT purls: - pkg:pypi/hatchling?source=hash-mapping - size: 56598 - timestamp: 1734311718682 + size: 60891 + timestamp: 1767609134323 - pypi: https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl name: humanfriendly version: '10.0' @@ -1257,47 +1243,48 @@ packages: - antlr4-python3-runtime==4.9.* - packaging - importlib-resources ; python_full_version < '3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e - md5: 8b189310083baabfb622af68fd9d3ae3 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 + md5: 186a18e3ba246eccfc7cff00cd19a870 depends: - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 + - libgcc >=14 + - libstdcxx >=14 license: MIT license_family: MIT purls: [] - size: 12129203 - timestamp: 1720853576813 -- pypi: https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl + size: 12728445 + timestamp: 1767969922681 +- pypi: https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl name: identify - version: 2.6.9 - sha256: c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150 + version: 2.6.16 + sha256: 391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0 requires_dist: - ukkonen ; extra == 'license' - requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl name: idna - version: '3.10' - sha256: 946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + version: '3.11' + sha256: 771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea requires_dist: - ruff>=0.6.2 ; extra == 'all' - mypy>=1.11.2 ; extra == 'all' - pytest>=8.3.2 ; extra == 'all' - flake8>=7.1.1 ; extra == 'all' - requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - sha256: 598951ebdb23e25e4cec4bbff0ae369cec65ead80b50bc08b441d8e54de5cf03 - md5: f4b39bf00c69f56ac01e020ebfac066c + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 + md5: 63ccfdc3a3ce25b027b8767eb722fca8 depends: - python >=3.9 - - zipp >=0.5 + - zipp >=3.20 + - python license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/importlib-metadata?source=hash-mapping - size: 29141 - timestamp: 1737420302391 + size: 34641 + timestamp: 1747934053147 - pypi: https://files.pythonhosted.org/packages/93/e8/facde510585869b5ec694e8e0363ffe4eba067cb357a8398a55f6a1f8023/importlib_resources-6.1.1-py3-none-any.whl name: importlib-resources version: 6.1.1 @@ -1320,23 +1307,24 @@ packages: - pytest-black>=0.3.7 ; platform_python_implementation != 'PyPy' and extra == 'testing' - pytest-mypy>=0.9.1 ; platform_python_implementation != 'PyPy' and extra == 'testing' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl name: iniconfig - version: 2.0.0 - sha256: b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 - requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - sha256: f1ac18b11637ddadc05642e8185a851c7fab5998c6f5470d716812fae943b2af - md5: 446bd6c8cb26050d528881df495ce646 + version: 2.3.0 + sha256: f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d depends: - markupsafe >=2.0 - - python >=3.9 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/jinja2?source=compressed-mapping - size: 112714 - timestamp: 1741263433881 + size: 120685 + timestamp: 1764517220861 - pypi: https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl name: jsonlines version: 4.0.0 @@ -1344,19 +1332,20 @@ packages: requires_dist: - attrs>=19.2.0 requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - sha256: 150c05a6e538610ca7c43beb3a40d65c90537497a4f6a5f4d15ec0451b6f5ebb - md5: 30186d27e2c9fa62b45fb1476b7200e3 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4 + md5: b38117a3c920364aff79f870c984b4a3 depends: - - libgcc-ng >=10.3.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: LGPL-2.1-or-later purls: [] - size: 117831 - timestamp: 1646151697040 -- pypi: https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + size: 134088 + timestamp: 1754905959823 +- pypi: https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: kiwisolver - version: 1.4.8 - sha256: cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a + version: 1.4.9 + sha256: f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04 requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 @@ -1373,116 +1362,181 @@ packages: purls: [] size: 1370023 timestamp: 1719463201255 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.17-h717163a_0.conda - sha256: d6a61830a354da022eae93fa896d0991385a875c6bba53c82263a289deda9db8 - md5: 000e85703f0fd9594c81710dd5066471 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.18-h0c24ade_0.conda + sha256: 836ec4b895352110335b9fdcfa83a8dcdbe6c5fb7c06c4929130600caea91c0a + md5: 6f2e2c8f58160147c4d1c6f4c14cbac4 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libtiff >=4.7.0,<4.8.0a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 license: MIT license_family: MIT purls: [] - size: 248046 - timestamp: 1739160907615 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - sha256: db73f38155d901a610b2320525b9dd3b31e4949215c870685fd92ea61b5ce472 - md5: 01f8d123c96816249efd255a31ad7712 + size: 249959 + timestamp: 1768184673131 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_105.conda + sha256: 1027bd8aa0d5144e954e426ab6218fd5c14e54a98f571985675468b339c808ca + md5: 3ec0aa5037d39b06554109a01e6fb0c6 depends: - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 constrains: - - binutils_impl_linux-64 2.43 + - binutils_impl_linux-64 2.45 license: GPL-3.0-only license_family: GPL purls: [] - size: 671240 - timestamp: 1740155456116 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - sha256: cb55f36dcd898203927133280ae1dc643368af041a48bcf7c026acb7c47b0c12 - md5: 76bbff344f0134279f225174e9064c8f + size: 730831 + timestamp: 1766513089214 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda + sha256: 412381a43d5ff9bbed82cd52a0bbca5b90623f62e41007c9c42d3870c60945ff + md5: 9344155d33912347b37f0ae6c410a835 depends: - - libgcc-ng >=12 - - libstdcxx-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 license: Apache-2.0 license_family: Apache purls: [] - size: 281798 - timestamp: 1657977462600 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - sha256: 143a586aa67d50622ef703de57b9d43f44945836d6568e0e7aa174bd8c45e0d4 - md5: 488f260ccda0afaf08acb286db439c2f + size: 264243 + timestamp: 1745264221534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250512.1-cxx17_hba17884_0.conda + sha256: dcd1429a1782864c452057a6c5bc1860f2b637dc20a2b7e6eacd57395bbceff8 + md5: 83b160d4da3e1e847bf044997621ed63 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libstdcxx >=13 constrains: - - libabseil-static =20240722.0=cxx17* - - abseil-cpp =20240722.0 + - libabseil-static =20250512.1=cxx17* + - abseil-cpp =20250512.1 license: Apache-2.0 license_family: Apache purls: [] - size: 1311599 - timestamp: 1736008414161 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda - build_number: 31 - sha256: 862289f2cfb84bb6001d0e3569e908b8c42d66b881bd5b03f730a3924628b978 - md5: bdf4a57254e8248222cb631db4393ff1 + size: 1310612 + timestamp: 1750194198254 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libavif16-1.3.0-h6395336_2.conda + sha256: e3a44c0eda23aa15c9a8dfa8c82ecf5c8b073e68a16c29edd0e409e687056d30 + md5: c09c4ac973f7992ba0c6bb1aafd77bd4 + depends: + - __glibc >=2.17,<3.0.a0 + - aom >=3.9.1,<3.10.0a0 + - dav1d >=1.2.1,<1.2.2.0a0 + - libgcc >=14 + - rav1e >=0.7.1,<0.8.0a0 + - svt-av1 >=3.1.2,<3.1.3.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 139399 + timestamp: 1756124751131 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h5875eb1_mkl.conda + build_number: 5 + sha256: 328d64d4eb51047c39a8039a30eb47695855829d0a11b72d932171cb1dcdfad3 + md5: 9d2f2e3a943d38f972ceef9cde8ba4bf depends: - - mkl >=2024.2.2,<2025.0a0 + - mkl >=2025.3.0,<2026.0a0 constrains: - - liblapack =3.9.0=31*_mkl - - liblapacke =3.9.0=31*_mkl - - blas =2.131=mkl - - libcblas =3.9.0=31*_mkl + - liblapack 3.11.0 5*_mkl + - liblapacke 3.11.0 5*_mkl + - libcblas 3.11.0 5*_mkl + - blas 2.305 mkl track_features: - blas_mkl + - blas_mkl_2 license: BSD-3-Clause license_family: BSD purls: [] - size: 17259 - timestamp: 1740087718283 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda - build_number: 31 - sha256: 2ee3ab2b6eeb59f2d3c6f933fa0db28f1b56f0bc543ed2c0f6ec04060e4b6ec0 - md5: 2a06a6c16b45bd3d10002927ca204b67 + size: 18744 + timestamp: 1765818556597 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e + md5: 72c8fd1af66bd67bf580645b426513ed + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 79965 + timestamp: 1764017188531 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + sha256: 12fff21d38f98bc446d82baa890e01fd82e3b750378fedc720ff93522ffb752b + md5: 366b40a69f0ad6072561c1d09301c886 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 34632 + timestamp: 1764017199083 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + sha256: a0c15c79997820bbd3fbc8ecf146f4fe0eca36cc60b62b63ac6cf78857f1dd0d + md5: 4ffbb341c8b616aa2494b6afb26a0c5f + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 hb03c661_1 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + size: 298378 + timestamp: 1764017210931 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_hfef963f_mkl.conda + build_number: 5 + sha256: 8352f472c49c42a83a20387b5f6addab1f910c5a62f4f5b8998d7dc89131ba2e + md5: 9b6cb3aa4b7912121c64b97a76ca43d5 depends: - - libblas 3.9.0 31_hfdb39a5_mkl + - libblas 3.11.0 5_h5875eb1_mkl constrains: - - liblapack =3.9.0=31*_mkl - - liblapacke =3.9.0=31*_mkl - - blas =2.131=mkl + - liblapack 3.11.0 5*_mkl + - liblapacke 3.11.0 5*_mkl + - blas 2.305 mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 16724 - timestamp: 1740087727554 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h4637d8d_4.conda - sha256: bc67b9b21078c99c6bd8595fe7e1ed6da1f721007726e717f0449de7032798c4 - md5: d4529f4dff3057982a7617c7ac58fde3 + size: 18385 + timestamp: 1765818571086 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda + sha256: cb83980c57e311783ee831832eb2c20ecb41e7dee6e86e8b70b8cef0e43eab55 + md5: d4a250da4737ee127fb1fa6452a9002e depends: - - krb5 >=1.21.1,<1.22.0a0 - - libgcc-ng >=12 - - libstdcxx-ng >=12 - - libzlib >=1.2.13,<2.0.0a0 + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.21.3,<1.22.0a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 license: Apache-2.0 license_family: Apache purls: [] - size: 4519402 - timestamp: 1689195353551 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.23-h4ddbbb0_0.conda - sha256: 511d801626d02f4247a04fff957cc6e9ec4cc7e8622bd9acd076bcdc5de5fe66 - md5: 8dfae1d2e74767e9ce36d5fa0d8605db + size: 4523621 + timestamp: 1749905341688 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libde265-1.0.15-h00ab1b0_0.conda + sha256: 7cf7e294e1a7c8219065885e186d8f52002fb900bf384d815f159b5874204e3d + md5: 407fee7a5d7ab2dca12c9ca7f62310ad + depends: + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: LGPL-3.0-or-later + license_family: LGPL + purls: [] + size: 411814 + timestamp: 1703088639063 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 + md5: 6c77a605a7a689d17d4819c0f8ac9a00 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 72255 - timestamp: 1734373823254 + size: 73490 + timestamp: 1761979956660 - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 md5: c277e0a4d549b03ac1e9d6cbbe3d017b @@ -1496,378 +1550,420 @@ packages: purls: [] size: 134676 timestamp: 1738479519902 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - sha256: 56541b98447b58e52d824bd59d6382d609e11de1f8adf20b23143e353d2b8d26 - md5: db833e03127376d461e1e13e76f09b6c +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - constrains: - - expat 2.6.4.* - license: MIT - license_family: MIT + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD purls: [] - size: 73304 - timestamp: 1730967041968 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda - sha256: 33ab03438aee65d6aa667cf7d90c91e5e7d734c19a67aa4c7040742c0a13d505 - md5: db0bfbe7dd197b68ad5f30333bae6ce0 + size: 112766 + timestamp: 1702146165126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda + sha256: 1e1b08f6211629cbc2efe7a5bca5953f8f6b3cae0eeb04ca4dacee1bd4e2db2f + md5: 8b09ae86839581147ef2e5c5e229d164 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 constrains: - - expat 2.7.0.* + - expat 2.7.3.* license: MIT license_family: MIT purls: [] - size: 74427 - timestamp: 1743431794976 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_0.conda - sha256: 67a6c95e33ebc763c1adc3455b9a9ecde901850eb2fceb8e646cc05ef3a663da - md5: e3eb7806380bc8bcecba6d749ad5f026 + size: 76643 + timestamp: 1763549731408 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda + sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 + md5: 35f29eec58405aaf55e01cb470d8c26a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 53415 - timestamp: 1739260413716 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.13.3-ha770c72_1.conda - sha256: 7be9b3dac469fe3c6146ff24398b685804dfc7a1de37607b84abd076f57cc115 - md5: 51f5be229d83ecd401fb369ab96ae669 + size: 57821 + timestamp: 1760295480630 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.1-ha770c72_0.conda + sha256: 4641d37faeb97cf8a121efafd6afd040904d4bca8c46798122f417c31d5dfbec + md5: f4084e4e6577797150f9b04a4560ceb0 depends: - - libfreetype6 >=2.13.3 + - libfreetype6 >=2.14.1 license: GPL-2.0-only OR FTL purls: [] - size: 7693 - timestamp: 1745369988361 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.13.3-h48d6fc4_1.conda - sha256: 7759bd5c31efe5fbc36a7a1f8ca5244c2eabdbeb8fc1bee4b99cf989f35c7d81 - md5: 3c255be50a506c50765a93a6644f32fe + size: 7664 + timestamp: 1757945417134 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.1-h73754d4_0.conda + sha256: 4a7af818a3179fafb6c91111752954e29d3a2a950259c14a2fc7ba40a8b03652 + md5: 8e7251989bca326a28f4a5ffbd74557a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libpng >=1.6.47,<1.7.0a0 + - libgcc >=14 + - libpng >=1.6.50,<1.7.0a0 - libzlib >=1.3.1,<2.0a0 constrains: - - freetype >=2.13.3 + - freetype >=2.14.1 license: GPL-2.0-only OR FTL purls: [] - size: 380134 - timestamp: 1745369987697 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h767d61c_2.conda - sha256: 3a572d031cb86deb541d15c1875aaa097baefc0c580b54dc61f5edab99215792 - md5: ef504d1acbd74b7cc6849ef8af47dd03 + size: 386739 + timestamp: 1757945416744 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda + sha256: 6eed58051c2e12b804d53ceff5994a350c61baf117ec83f5f10c953a3f311451 + md5: 6d0363467e6ed84f11435eb309f2ff06 depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgomp 14.2.0 h767d61c_2 - - libgcc-ng ==14.2.0=*_2 + - libgcc-ng ==15.2.0=*_16 + - libgomp 15.2.0 he0feb66_16 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 847885 - timestamp: 1740240653082 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_2.conda - sha256: fb7558c328b38b2f9d2e412c48da7890e7721ba018d733ebdfea57280df01904 - md5: a2222a6ada71fb478682efe483ce0f92 + size: 1042798 + timestamp: 1765256792743 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda + sha256: 5f07f9317f596a201cc6e095e5fc92621afca64829785e483738d935f8cab361 + md5: 5a68259fac2da8f2ee6f7bfe49c9eb8b depends: - - libgcc 14.2.0 h767d61c_2 + - libgcc 15.2.0 he0feb66_16 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 53758 - timestamp: 1740240660904 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_2.conda - sha256: e05263e8960da03c341650f2a3ffa4ccae4e111cb198e8933a2908125459e5a6 - md5: fb54c4ea68b460c278d26eea89cfbcc3 + size: 27256 + timestamp: 1765256804124 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_16.conda + sha256: 8a7b01e1ee1c462ad243524d76099e7174ebdd94ff045fe3e9b1e58db196463b + md5: 40d9b534410403c821ff64f00d0adc22 depends: - - libgfortran5 14.2.0 hf1ad2bd_2 + - libgfortran5 15.2.0 h68bc16d_16 constrains: - - libgfortran-ng ==14.2.0=*_2 + - libgfortran-ng ==15.2.0=*_16 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 53733 - timestamp: 1740240690977 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hf1ad2bd_2.conda - sha256: c17b7cf3073a1f4e1f34d50872934fa326346e104d3c445abc1e62481ad6085c - md5: 556a4fdfac7287d349b8f09aba899693 + size: 27215 + timestamp: 1765256845586 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_16.conda + sha256: d0e974ebc937c67ae37f07a28edace978e01dc0f44ee02f29ab8a16004b8148b + md5: 39183d4e0c05609fd65f130633194e37 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=14.2.0 + - libgcc >=15.2.0 constrains: - - libgfortran 14.2.0 + - libgfortran 15.2.0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 1461978 - timestamp: 1740240671964 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.84.1-h3618099_1.conda - sha256: 1d57e4b03fbe0d83f62ac5ccb5d7f65e6e59b108741e67645d35dcde50cb5264 - md5: 714c97d4ff495ab69d1fdfcadbcae985 + size: 2480559 + timestamp: 1765256819588 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.3-h6548e54_0.conda + sha256: 82d6c2ee9f548c84220fb30fb1b231c64a53561d6e485447394f0a0eeeffe0e6 + md5: 034bea55a4feef51c98e8449938e9cee depends: - __glibc >=2.17,<3.0.a0 - - libffi >=3.4.6,<3.5.0a0 - - libgcc >=13 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 - libiconv >=1.18,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - pcre2 >=10.45,<10.46.0a0 + - pcre2 >=10.47,<10.48.0a0 constrains: - - glib 2.84.1 *_1 + - glib 2.86.3 *_0 license: LGPL-2.1-or-later purls: [] - size: 3939065 - timestamp: 1746083931235 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.68.2-h25350d4_0.conda - sha256: 7bc18db31752d6c445efa771a23d859a4d7e19324ac355fbbfa945024e3003a3 - md5: 648f0e331c20e9ff3986f253b3a1d439 + size: 3946542 + timestamp: 1765221858705 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.74.1-h3288cfb_1.conda + sha256: 5a947f6ad314b430d54d17e8d13015dd98f902a928e2a3d36543d628bdd2208d + md5: 8142b6a4f2ffd81791262c8bd725b655 depends: - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.4,<2.0a0 + - c-ares >=1.34.5,<2.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 - - libre2-11 >=2024.7.2 - - libstdcxx >=13 + - libabseil >=20250512.1,<20250513.0a0 + - libgcc >=14 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libre2-11 >=2025.8.12 + - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.4.1,<4.0a0 + - openssl >=3.5.4,<4.0a0 - re2 constrains: - - grpc-cpp =1.68.2 + - grpc-cpp =1.74.1 license: Apache-2.0 license_family: APACHE purls: [] - size: 7783962 - timestamp: 1740918408589 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - sha256: d14c016482e1409ae1c50109a9ff933460a50940d2682e745ab1c172b5282a69 - md5: 804ca9e91bcaea0824a341d55b1684f2 + size: 8902682 + timestamp: 1759629580676 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libheif-1.19.7-gpl_hc18d805_100.conda + sha256: ec9797d57088aeed7ca4905777d4f3e70a4dbe90853590eef7006b0ab337af3f + md5: 1db2693fa6a50bef58da2df97c5204cb depends: - __glibc >=2.17,<3.0.a0 + - aom >=3.9.1,<3.10.0a0 + - dav1d >=1.2.1,<1.2.2.0a0 + - libavif16 >=1.2.0,<2.0a0 + - libde265 >=1.0.15,<1.0.16.0a0 - libgcc >=13 - libstdcxx >=13 - - libxml2 >=2.13.4,<3.0a0 + - x265 >=3.5,<3.6.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: [] + size: 596714 + timestamp: 1741306859216 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.12.2-default_hafda6a7_1000.conda + sha256: 2cf160794dda62cf93539adf16d26cfd31092829f2a2757dbdd562984c1b110a + md5: 0ed3aa3e3e6bc85050d38881673a692f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 license: BSD-3-Clause license_family: BSD purls: [] - size: 2423200 - timestamp: 1731374922090 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda - sha256: 18a4afe14f731bfb9cf388659994263904d20111e42f841e9eea1bb6f91f4ab4 - md5: e796ff8ddc598affdf7c173d6145f087 + size: 2449916 + timestamp: 1765103845133 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f + md5: 915f5995e94f60e9a4826e0b0920ee88 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: LGPL-2.1-only purls: [] - size: 713084 - timestamp: 1740128065462 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.0.0-hd590300_1.conda - sha256: b954e09b7e49c2f2433d6f3bb73868eda5e378278b0f8c1dd10a7ef090e14f2f - md5: ea25936bb4080d843790b586850f82b8 + size: 790176 + timestamp: 1754908768807 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda + sha256: cc9aba923eea0af8e30e0f94f2ad7156e2984d80d1e8e7fe6be5a1f257f0eb32 + md5: 8397539e3a0bbd1695584fb4f927485a depends: - - libgcc-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 constrains: - jpeg <0.0.0a license: IJG AND BSD-3-Clause AND Zlib purls: [] - size: 618575 - timestamp: 1694474974816 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda - build_number: 31 - sha256: a2d20845d916ac8fba09376cd791136a9b4547afb2131bc315178adfc87bb4ca - md5: 10d012ddd7cc1c7ff9093d4974a34e53 + size: 633710 + timestamp: 1762094827865 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h5e43f62_mkl.conda + build_number: 5 + sha256: b411a9dccb21cd6231f8f66b63916a6520a7b23363e6f9d1d111e8660f2798b0 + md5: 88155c848e1278b0990692e716c9eab4 depends: - - libblas 3.9.0 31_hfdb39a5_mkl + - libblas 3.11.0 5_h5875eb1_mkl constrains: - - liblapacke =3.9.0=31*_mkl - - blas =2.131=mkl - - libcblas =3.9.0=31*_mkl + - liblapacke 3.11.0 5*_mkl + - libcblas 3.11.0 5*_mkl + - blas 2.305 mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 16760 - timestamp: 1740087736615 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - sha256: cad52e10319ca4585bc37f0bc7cce99ec7c15dc9168e42ccb96b741b0a27db3f - md5: 42d5b6a0f30d3c10cd88cb8584fda1cb + size: 18398 + timestamp: 1765818583873 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda + sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb + md5: c7c83eecbb72d88b940c249af56c8b17 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 + constrains: + - xz 5.8.2.* license: 0BSD purls: [] - size: 111357 - timestamp: 1738525339684 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 - md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 + size: 113207 + timestamp: 1768752626120 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 + md5: b499ce4b026493a13774bcf0f4c33849 depends: - - libgcc-ng >=12 + - __glibc >=2.17,<3.0.a0 + - c-ares >=1.34.5,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libev >=4.33,<5.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - openssl >=3.5.2,<4.0a0 + license: MIT + license_family: MIT + purls: [] + size: 666600 + timestamp: 1756834976695 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: LGPL-2.1-only license_family: GPL purls: [] - size: 33408 - timestamp: 1697359010159 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda - sha256: 23367d71da58c9a61c8cbd963fcffb92768d4ae5ffbef9a47cdf1f54f98c5c36 - md5: 55199e2ae2c3651f6f9b2a447b47bdc9 + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.54-h421ea60_0.conda + sha256: 5de60d34aac848a9991a09fcdea7c0e783d00024aefec279d55e87c0c44742cd + md5: d361fa2a59e53b61c2675bfa073e5b7e depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 - libzlib >=1.3.1,<2.0a0 license: zlib-acknowledgement purls: [] - size: 288701 - timestamp: 1739952993639 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - sha256: 51125ebb8b7152e4a4e69fd2398489c4ec8473195c27cde3cbdf1cb6d18c5493 - md5: d8703f1ffe5a06356f06467f1d0b9464 + size: 317435 + timestamp: 1768285668880 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.31.1-h49aed37_4.conda + sha256: 0ef142ac31e6fd59b4af89ac800acb6deb3fbd9cc4ccf070c03cc2c784dc7296 + md5: 07479fc04ba3ddd5d9f760ef1635cfa7 depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - - libgcc >=13 - - libstdcxx >=13 + - libabseil >=20250512.1,<20250513.0a0 + - libgcc >=14 + - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 2960815 - timestamp: 1735577210663 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - sha256: 4420f8362c71251892ba1eeb957c5e445e4e1596c0c651c28d0d8b415fe120c7 - md5: b2fede24428726dd867611664fb372e8 + size: 4372578 + timestamp: 1766316228461 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h7b12aa8_0.conda + sha256: eb5d5ef4d12cdf744e0f728b35bca910843c8cf1249f758cf15488ca04a21dbb + md5: a30848ebf39327ea078cf26d114cff53 depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - - libgcc >=13 - - libstdcxx >=13 + - libabseil >=20250512.1,<20250513.0a0 + - libgcc >=14 + - libstdcxx >=14 constrains: - - re2 2024.07.02.* + - re2 2025.11.05.* license: BSD-3-Clause license_family: BSD purls: [] - size: 209793 - timestamp: 1735541054068 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda - sha256: 7a09eef804ef7cf4d88215c2297eabb72af8ad0bd5b012060111c289f14bbe7d - md5: 73cea06049cc4174578b432320a003b8 + size: 211099 + timestamp: 1762397758105 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.2-hf4e2dac_0.conda + sha256: 04596fcee262a870e4b7c9807224680ff48d4d0cc0dac076a602503d3dc6d217 + md5: da5be73701eecd0e8454423fd6ffcf30 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - icu >=78.2,<79.0a0 + - libgcc >=14 - libzlib >=1.3.1,<2.0a0 - license: Unlicense + license: blessing purls: [] - size: 915956 - timestamp: 1739953155793 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - sha256: 8f5bd92e4a24e1d35ba015c5252e8f818898478cb3bc50bd8b12ab54707dc4da - md5: a78c856b6dc6bf4ea8daeb9beaaa3fb0 + size: 942808 + timestamp: 1768147973361 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda + sha256: 813427918316a00c904723f1dfc3da1bbc1974c5cfe1ed1e704c6f4e0798cbc6 + md5: 68f68355000ec3f1d6f26ea13e8f525f depends: - __glibc >=2.17,<3.0.a0 - - libgcc 14.2.0 h767d61c_2 + - libgcc 15.2.0 he0feb66_16 + constrains: + - libstdcxx-ng ==15.2.0=*_16 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 3884556 - timestamp: 1740240685253 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda - sha256: e86f38b007cf97cc2c67cd519f2de12a313c4ee3f5ef11652ad08932a5e34189 - md5: c75da67f045c2627f59e6fcb5f4e3a9b + size: 5856456 + timestamp: 1765256838573 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda + sha256: 81f2f246c7533b41c5e0c274172d607829019621c4a0823b5c0b4a8c7028ee84 + md5: 1b3152694d236cf233b76b8c56bf0eae depends: - - libstdcxx 14.2.0 h8f9b012_2 + - libstdcxx 15.2.0 h934c35e_16 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL purls: [] - size: 53830 - timestamp: 1740240722530 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.0-hd9ff511_3.conda - sha256: b224e16b88d76ea95e4af56e2bc638c603bd26a770b98d117d04541d3aafa002 - md5: 0ea6510969e1296cc19966fad481f6de + size: 27300 + timestamp: 1765256885128 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 + md5: cd5a90476766d53e901500df9215e927 depends: - __glibc >=2.17,<3.0.a0 - lerc >=4.0.0,<5.0a0 - - libdeflate >=1.23,<1.24.0a0 - - libgcc >=13 - - libjpeg-turbo >=3.0.0,<4.0a0 - - liblzma >=5.6.3,<6.0a0 - - libstdcxx >=13 - - libwebp-base >=1.4.0,<2.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.0,<4.0a0 + - liblzma >=5.8.1,<6.0a0 + - libstdcxx >=14 + - libwebp-base >=1.6.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - zstd >=1.5.6,<1.6.0a0 + - zstd >=1.5.7,<1.6.0a0 license: HPND purls: [] - size: 428173 - timestamp: 1734398813264 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.6.0-cpu_mkl_hc5f969b_101.conda - sha256: e4ffe6f2ba0a6d7b0098137a289c71d14188511e52b4e8a65be3230b5bb1cce8 - md5: 284859a044d1c9b9e1c0a29cee771305 + size: 435273 + timestamp: 1762022005702 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.9.1-cpu_mkl_hfee2a32_103.conda + sha256: 710f481b59db5aebc9e7d50803e214792e0ddd35f71c08ba1204c04ae46c6af6 + md5: c39901fc181701c54648a8580d027bcb depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 + - fmt >=12.1.0,<12.2.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250512.1,<20250513.0a0 - libblas * *mkl - - libcblas >=3.9.0,<4.0a0 - - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 - - libstdcxx >=13 - - libuv >=1.50.0,<2.0a0 + - libcblas >=3.11.0,<4.0a0 + - libgcc >=14 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libstdcxx >=14 + - libuv >=1.51.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=19.1.7 - - mkl >=2024.2.2,<2025.0a0 - - sleef >=3.8,<4.0a0 + - llvm-openmp >=21.1.8 + - mkl >=2025.3.0,<2026.0a0 + - pybind11-abi 11 + - sleef >=3.9.0,<4.0a0 constrains: - - pytorch-cpu ==2.6.0 - - pytorch 2.6.0 cpu_mkl_*_101 - - pytorch-gpu ==99999999 + - pytorch-gpu <0.0a0 + - pytorch-cpu 2.9.1 + - pytorch 2.9.1 cpu_mkl_*_103 license: BSD-3-Clause license_family: BSD purls: [] - size: 54364840 - timestamp: 1741568386963 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 - md5: 40b61aab5c7ba9ff276c41cfffe6b80b + size: 60717752 + timestamp: 1768338409325 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda + sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee + md5: db409b7c1720428638e7c0d509d3e1b5 depends: - - libgcc-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 license: BSD-3-Clause license_family: BSD purls: [] - size: 33601 - timestamp: 1680112270483 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - sha256: b4a8890023902aef9f1f33e3e35603ad9c2f16c21fdb58e968fa6c1bd3e94c0b - md5: 771ee65e13bc599b0b62af5359d80169 + size: 40311 + timestamp: 1766271528534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b + md5: 0f03292cc56bf91a077a134ea8747118 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 891272 - timestamp: 1737016632446 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.5.0-h851e524_0.conda - sha256: c45283fd3e90df5f0bd3dbcd31f59cdd2b001d424cf30a07223655413b158eaf - md5: 63f790534398730f59e1b899c3644d4a + size: 895108 + timestamp: 1753948278280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b + md5: aea31d2e5b1091feca96fcfe945c3cf9 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 constrains: - - libwebp 1.5.0 + - libwebp 1.6.0 license: BSD-3-Clause license_family: BSD purls: [] - size: 429973 - timestamp: 1734777489810 + size: 429011 + timestamp: 1752159441324 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa md5: 92ed62436b625154323d40d5f2f11dd7 @@ -1891,21 +1987,39 @@ packages: purls: [] size: 100393 timestamp: 1702724383534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.6-h8d12d68_0.conda - sha256: db8af71ea9c0ae95b7cb4a0f59319522ed2243942437a1200ceb391493018d85 - md5: 328382c0e0ca648e5c189d5ec336c604 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-he237659_1.conda + sha256: 047be059033c394bd32ae5de66ce389824352120b3a7c0eff980195f7ed80357 + md5: 417955234eccd8f252b86a265ccdab7f depends: - __glibc >=2.17,<3.0.a0 - - icu >=75.1,<76.0a0 - - libgcc >=13 + - icu >=78.1,<79.0a0 + - libgcc >=14 - libiconv >=1.18,<2.0a0 - - liblzma >=5.6.4,<6.0a0 + - liblzma >=5.8.1,<6.0a0 + - libxml2-16 2.15.1 hca6bf5a_1 - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT purls: [] - size: 690296 - timestamp: 1739952967309 + size: 45402 + timestamp: 1766327161688 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-hca6bf5a_1.conda + sha256: 8331284bf9ae641b70cdc0e5866502dd80055fc3b9350979c74bb1d192e8e09e + md5: 3fdd8d99683da9fe279c2f4cecd1e048 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.1,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 + - libzlib >=1.3.1,<2.0a0 + constrains: + - libxml2 2.15.1 + license: MIT + license_family: MIT + purls: [] + size: 555747 + timestamp: 1766327145986 - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 @@ -1919,47 +2033,48 @@ packages: purls: [] size: 60963 timestamp: 1727963148474 -- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.7-h024ca30_0.conda - sha256: 5383e32604e03814b6011fa01a5332057934181a7ea0e90abba7890c17cabce6 - md5: 9915f85a72472011550550623cce2d53 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-21.1.8-h4922eb0_0.conda + sha256: a5a7ad16eecbe35cac63e529ea9c261bef4ccdd68cb1db247409f04529423989 + md5: f8640b709b37dc7758ddce45ea18d000 depends: - __glibc >=2.17,<3.0.a0 constrains: - - openmp 19.1.7|19.1.7.* + - intel-openmp <0.0a0 + - openmp 21.1.8|21.1.8.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE purls: [] - size: 3190529 - timestamp: 1736986301022 -- pypi: https://files.pythonhosted.org/packages/c5/f8/309546aec092434166a6e11c7dcecb5c2d0a787c18c072d61e18da9eba57/lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl + size: 6127279 + timestamp: 1765964409311 +- pypi: https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl name: lxml - version: 5.3.1 - sha256: d184f85ad2bb1f261eac55cddfcf62a70dee89982c978e92b9a74a1bfef2e367 + version: 6.0.2 + sha256: 90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192 requires_dist: - - cython>=3.0.11,<3.1.0 ; extra == 'source' - cssselect>=0.7 ; extra == 'cssselect' - html5lib ; extra == 'html5' - beautifulsoup4 ; extra == 'htmlsoup' - lxml-html-clean ; extra == 'html-clean' - requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.6-pyhd8ed1ab_0.conda - sha256: fce1fde00359696983989699c00f9891194c4ebafea647a8d21b7e2e3329b56e - md5: 06e9bebf748a0dea03ecbe1f0e27e909 + requires_python: '>=3.8' +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.1-pyhcf101f3_0.conda + sha256: 40e45b3e57a6b3c61e9ca0bf8526fec9814dca2be4115245966946e1192983ef + md5: 8a5f9a177a2ea05c66237e6f2eaece60 depends: - importlib-metadata >=4.4 - - python >=3.6 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/markdown?source=hash-mapping - size: 78331 - timestamp: 1710435316163 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - sha256: 4a6bf68d2a2b669fecc9a4a009abd1cf8e72c2289522ff00d81b5a6e51ae78f5 - md5: eb227c3e0bf58f5bd69c0532b157975b + - pkg:pypi/markdown?source=compressed-mapping + size: 85359 + timestamp: 1769079901411 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_0.conda + sha256: f77f9f1a4da45cbc8792d16b41b6f169f649651a68afdc10b2da9da12b9aa42b + md5: f775a43412f7f3d7ed218113ad233869 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 constrains: @@ -1968,12 +2083,12 @@ packages: license_family: BSD purls: - pkg:pypi/markupsafe?source=hash-mapping - size: 24604 - timestamp: 1733219911494 -- pypi: https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + size: 25321 + timestamp: 1759055268795 +- pypi: https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: matplotlib - version: 3.10.3 - sha256: ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a + version: 3.10.8 + sha256: 3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04 requires_dist: - contourpy>=1.0.1 - cycler>=0.10 @@ -1982,7 +2097,7 @@ packages: - numpy>=1.23 - packaging>=20.0 - pillow>=8 - - pyparsing>=2.3.1 + - pyparsing>=3 - python-dateutil>=2.7 - meson-python>=0.13.1,<0.17.0 ; extra == 'dev' - pybind11>=2.13.2,!=2.13.3 ; extra == 'dev' @@ -1992,7 +2107,7 @@ packages: - pypi: ./ name: mineagent version: 0.0.1 - sha256: 47afe98766c6a76acdcee5477183935d6deee083e2b26e699fe7d7c358c47f8d + sha256: 855a96eeea99e258cc9f49e313cc217b4040768c29f9cf2618b01193bccaeb67 requires_dist: - pyyaml - dacite @@ -2003,8 +2118,8 @@ packages: - torchvision - tqdm - tensorboard + - pyright - pre-commit ; extra == 'dev' - - pyright ; extra == 'dev' - pytest ; extra == 'dev' - pytest-mock ; extra == 'dev' - ruff ; extra == 'dev' @@ -2039,19 +2154,22 @@ packages: - hydra-core - pillow requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - sha256: 77906b0acead8f86b489da46f53916e624897338770dbf70b04b8f673c9273c1 - md5: 1459379c79dda834673426504d52b319 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2025.3.0-h0e700b2_463.conda + sha256: 659d79976f06d2b796a0836414573a737a0856b05facfa77e5cc114081a8b3d4 + md5: f121ddfc96e6a93a26d85906adf06208 depends: + - __glibc >=2.17,<3.0.a0 - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - - llvm-openmp >=19.1.2 - - tbb 2021.* + - libgcc >=14 + - libstdcxx >=14 + - llvm-openmp >=21.1.8 + - tbb >=2022.3.0 license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary purls: [] - size: 124718448 - timestamp: 1730231808335 + size: 125728406 + timestamp: 1767634121080 - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda sha256: 1bf794ddf2c8b3a3e14ae182577c624fa92dea975537accff4bc7e5fea085212 md5: aa14b9a5196a6d8dd364164b7ce56acf @@ -2088,18 +2206,18 @@ packages: - pkg:pypi/mpmath?source=hash-mapping size: 439705 timestamp: 1733302781386 -- pypi: https://files.pythonhosted.org/packages/a4/69/d3f343a61a2f86ef10ed7865a26beda7c71554136ce187b0384b1c2c9ca3/multiprocess-0.70.17-py312-none-any.whl +- pypi: https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl name: multiprocess - version: 0.70.17 - sha256: 2818af14c52446b9617d1b0755fa70ca2f77c28b25ed97bdaa2c69a22c47b46c + version: 0.70.19 + sha256: 3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28 requires_dist: - - dill>=0.3.9 - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl + - dill>=0.4.1 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl name: mypy-extensions - version: 1.0.0 - sha256: 4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d - requires_python: '>=3.5' + version: 1.1.0 + sha256: 1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 md5: 47e340acb35de30501a76c7c799c41d7 @@ -2110,27 +2228,53 @@ packages: purls: [] size: 891641 timestamp: 1738195959188 -- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - sha256: 39625cd0c9747fa5c46a9a90683b8997d8b9649881b3dc88336b13b7bdd60117 - md5: fd40bf7f7f4bc4b647dc8512053d9873 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 + md5: a2c1eeadae7a309daed9d62c96012a2b depends: - - python >=3.10 + - python >=3.11 - python constrains: - - numpy >=1.24 - - scipy >=1.10,!=1.11.0,!=1.11.1 - - matplotlib >=3.7 + - numpy >=1.25 + - scipy >=1.11.2 + - matplotlib-base >=3.8 - pandas >=2.0 license: BSD-3-Clause license_family: BSD - purls: [] - size: 1265008 - timestamp: 1731521053408 -- pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl + purls: + - pkg:pypi/networkx?source=compressed-mapping + size: 1587439 + timestamp: 1765215107045 +- pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl name: nodeenv - version: 1.9.1 - sha256: ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 + version: 1.10.0 + sha256: 5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*' +- conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-25.2.1-h728ac57_2.conda + sha256: 553164f7b041eb3e4cb22c1e4e6f3da532632416d3a7fa4fa97fa48e64f1eb85 + md5: 4c05dc4998537fabe4b545d9ef9d0a13 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.28,<3.0.a0 + - icu >=78.2,<79.0a0 + - libzlib >=1.3.1,<2.0a0 + - libuv >=1.51.0,<2.0a0 + - libnghttp2 >=1.67.0,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + - c-ares >=1.34.6,<2.0a0 + - libsqlite >=3.51.2,<4.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - openssl >=3.5.4,<4.0a0 + - libabseil >=20250512.1,<20250513.0a0 + - libabseil * cxx17* + license: MIT + license_family: MIT + purls: [] + size: 17243215 + timestamp: 1769159690239 - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda sha256: fe3459c75cf84dcef6ef14efcc4adb0ade66038ddd27cadb894f34f4797687d8 md5: d8285bea2a350f63fab23bf460221f3f @@ -2175,23 +2319,24 @@ packages: - numpy>=1.17.3 ; python_full_version >= '3.8' - numpy>=1.19.3 ; python_full_version >= '3.9' requires_python: '>=3.6' -- conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.6-h53dfc1b_2.conda - sha256: e9f7f19e6d5b004e9f6702a5098a64fdccd2cde88bbb5ad19f908a24dafcee8d - md5: ba099c7bb64899bbc06425ae02c0e926 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-21.0.9-h5755bd7_0.conda + sha256: 6ddcf5c40b955ec7a4db347c02ceae440f2f2fe1a930109f5184bb89e7247fc3 + md5: bcca45d4833097f666e0651a979699a1 depends: - __glibc >=2.17,<3.0.a0 - - alsa-lib >=1.2.13,<1.3.0a0 + - alsa-lib >=1.2.14,<1.3.0a0 - fontconfig >=2.15.0,<3.0a0 - fonts-conda-ecosystem - - freetype >=2.13.3,<3.0a0 - giflib >=5.2.2,<5.3.0a0 - - harfbuzz >=11.0.0,<12.0a0 + - harfbuzz >=12.2.0 - lcms2 >=2.17,<3.0a0 - libcups >=2.3.3,<2.4.0a0 - - libgcc >=13 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libpng >=1.6.47,<1.7.0a0 - - libstdcxx >=13 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libjpeg-turbo >=3.1.2,<4.0a0 + - libpng >=1.6.50,<1.7.0a0 + - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - xorg-libx11 >=1.8.12,<2.0a0 - xorg-libxext >=1.3.6,<2.0a0 @@ -2203,147 +2348,151 @@ packages: license: GPL-2.0-or-later WITH Classpath-exception-2.0 license_family: GPL purls: [] - size: 181272542 - timestamp: 1743200760089 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.3-h5fbd93e_0.conda - sha256: 5bee706ea5ba453ed7fd9da7da8380dd88b865c8d30b5aaec14d2b6dd32dbc39 - md5: 9e5816bc95d285c115a3ebc2f8563564 + size: 181489066 + timestamp: 1762396341087 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d + md5: 11b3379b191f63139e29c0d19dee24cd depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libpng >=1.6.44,<1.7.0a0 - - libstdcxx >=13 - - libtiff >=4.7.0,<4.8.0a0 + - libgcc >=14 + - libpng >=1.6.50,<1.7.0a0 + - libstdcxx >=14 + - libtiff >=4.7.1,<4.8.0a0 - libzlib >=1.3.1,<2.0a0 license: BSD-2-Clause license_family: BSD purls: [] - size: 342988 - timestamp: 1733816638720 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - sha256: cbf62df3c79a5c2d113247ddea5658e9ff3697b6e741c210656e239ecaf1768f - md5: 41adf927e746dc75ecf0ef841c454e48 + size: 355400 + timestamp: 1758489294972 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda + sha256: a47271202f4518a484956968335b2521409c8173e123ab381e775c358c67fe6d + md5: 9ee58d5c534af06558933af3c845a780 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates - - libgcc >=13 + - libgcc >=14 license: Apache-2.0 license_family: Apache purls: [] - size: 2939306 - timestamp: 1739301879343 -- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.14.1-py312h68727a3_0.conda - sha256: 8fd7b4433cc8bef4570904343b562b27da8aa566eae68b6166113a635f07dbb1 - md5: 846469b9895b87452453408a51d06a81 + size: 3165399 + timestamp: 1762839186699 +- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.18.0-py312hd9148b4_0.conda + sha256: f34a33825d0e925c6b0e09c81632657935e2c0cc595d2a70d9902c7b9f891a51 + md5: 4d4148297810361256ebf85b17693dff depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 + - libgcc >=14 + - libstdcxx >=14 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - typing-extensions >=4.5 + - typing-extensions >=4.12 license: Apache-2.0 license_family: Apache purls: - pkg:pypi/optree?source=hash-mapping - size: 374962 - timestamp: 1740912362915 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - sha256: da157b19bcd398b9804c5c52fc000fcb8ab0525bdb9c70f95beaa0bb42f85af1 - md5: 3bfed7e6228ebf2f7b9eaa47f1b4e2aa + size: 444884 + timestamp: 1763124490090 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda + sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 + md5: b76541e68fea4d511b1ac46a28dcd2c6 depends: - python >=3.8 + - python license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/packaging?source=hash-mapping - size: 60164 - timestamp: 1733203368787 -- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_1.conda - sha256: 9f64009cdf5b8e529995f18e03665b03f5d07c0b17445b8badef45bde76249ee - md5: 617f15191456cc6a13db418a275435e5 + - pkg:pypi/packaging?source=compressed-mapping + size: 72010 + timestamp: 1769093650580 +- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.3-pyhd8ed1ab_0.conda + sha256: 9b046bd271421cec66650f770b66f29692bcbfc4cfe40b24487eae396d2bcf26 + md5: 0485a8731a6d82f181e0e073a2e39a39 depends: - - python >=3.9 + - python >=3.10 license: MPL-2.0 license_family: MOZILLA purls: - pkg:pypi/pathspec?source=hash-mapping - size: 41075 - timestamp: 1733233471940 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.45-hc749103_0.conda - sha256: 27c4014f616326240dcce17b5f3baca3953b6bc5f245ceb49c3fa1e6320571eb - md5: b90bece58b4c2bf25969b70f3be42d25 + size: 53364 + timestamp: 1767999155326 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + sha256: 5e6f7d161356fefd981948bea5139c5aa0436767751a6930cb1ca801ebb113ff + md5: 7a3bff861a6583f1889021facefc08b1 depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - - libgcc >=13 + - libgcc >=14 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 1197308 - timestamp: 1745955064657 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-11.1.0-py312h80c1187_0.conda - sha256: 5c347962202b55ae4d8a463e0555c5c6ca33396266a08284bf1384399894e541 - md5: d3894405f05b2c0f351d5de3ae26fa9c + size: 1222481 + timestamp: 1763655398280 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.1.0-py312h50c33e8_0.conda + sha256: dc15482aadc863e2b65757b13a248971e832036bf5ccd2be48d02dfe4c1cf5e0 + md5: 923b06ad75b7acc888fa20a22dc397cd depends: + - python - __glibc >=2.17,<3.0.a0 - - freetype >=2.12.1,<3.0a0 - - lcms2 >=2.16,<3.0a0 - - libgcc >=13 - - libjpeg-turbo >=3.0.0,<4.0a0 - - libtiff >=4.7.0,<4.8.0a0 - - libwebp-base >=1.5.0,<2.0a0 - - libxcb >=1.17.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 - - openjpeg >=2.5.3,<3.0a0 - - python >=3.12,<3.13.0a0 - - python_abi 3.12.* *_cp312 + - libgcc >=14 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 - tk >=8.6.13,<8.7.0a0 + - python_abi 3.12.* *_cp312 + - libxcb >=1.17.0,<2.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libjpeg-turbo >=3.1.2,<4.0a0 + - zlib-ng >=2.3.2,<2.4.0a0 + - lcms2 >=2.17,<3.0a0 + - openjpeg >=2.5.4,<3.0a0 + - libwebp-base >=1.6.0,<2.0a0 license: HPND purls: - pkg:pypi/pillow?source=hash-mapping - size: 42749785 - timestamp: 1735929845390 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.0-h29eaf8c_0.conda - sha256: 1330c3fd424fa2deec6a30678f235049c0ed1b0fad8d2d81ef995c9322d5e49a - md5: d2f1c87d4416d1e7344cf92b1aaee1c4 + size: 1029473 + timestamp: 1767353193448 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + sha256: 43d37bc9ca3b257c5dd7bf76a8426addbdec381f6786ff441dc90b1a49143b6a + md5: c01af13bdc553d1a8fbfff6e8db075f0 depends: + - libgcc >=14 + - libstdcxx >=14 + - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 license: MIT license_family: MIT purls: [] - size: 398664 - timestamp: 1746011575217 -- pypi: https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl + size: 450960 + timestamp: 1754665235234 +- pypi: https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl name: platformdirs - version: 4.3.6 - sha256: 73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb + version: 4.5.1 + sha256: d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31 requires_dist: - - furo>=2024.8.6 ; extra == 'docs' + - furo>=2025.9.25 ; extra == 'docs' - proselint>=0.14 ; extra == 'docs' - - sphinx-autodoc-typehints>=2.4 ; extra == 'docs' - - sphinx>=8.0.2 ; extra == 'docs' + - sphinx-autodoc-typehints>=3.2 ; extra == 'docs' + - sphinx>=8.2.3 ; extra == 'docs' - appdirs==1.4.4 ; extra == 'test' - covdefaults>=2.3 ; extra == 'test' - - pytest-cov>=5 ; extra == 'test' - - pytest-mock>=3.14 ; extra == 'test' - - pytest>=8.3.2 ; extra == 'test' - - mypy>=1.11.2 ; extra == 'type' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - sha256: 122433fc5318816b8c69283aaf267c73d87aa2d09ce39f64c9805c9a3b264819 - md5: e9dcbce5f45f9ee500e728ae58b605b6 + - pytest-cov>=7 ; extra == 'test' + - pytest-mock>=3.15.1 ; extra == 'test' + - pytest>=8.4.2 ; extra == 'test' + - mypy>=1.18.2 ; extra == 'type' + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e + md5: d7585b6550ad04c8c5e21097ada2888e depends: - python >=3.9 + - python license: MIT license_family: MIT purls: - - pkg:pypi/pluggy?source=hash-mapping - size: 23595 - timestamp: 1733222855563 + - pkg:pypi/pluggy?source=compressed-mapping + size: 25877 + timestamp: 1764896838868 - pypi: https://files.pythonhosted.org/packages/73/ca/60ec131c3b43bff58261167045778b2509b83922ce8f935ac89d871bd3ea/praw-7.8.1-py3-none-any.whl name: praw version: 7.8.1 @@ -2383,44 +2532,49 @@ packages: - pytest>=2.7.3 ; extra == 'test' - urllib3==1.26.*,<2 ; extra == 'test' requires_python: ~=3.8 -- pypi: https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl name: pre-commit - version: 4.1.0 - sha256: d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b + version: 4.5.1 + sha256: 3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77 requires_dist: - cfgv>=2.0.0 - identify>=1.0.0 - nodeenv>=0.11.1 - pyyaml>=5.1 - virtualenv>=20.10.0 - requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-5.28.3-py312h2ec8cdc_0.conda - sha256: acb2e0ee948e3941f8ed191cb77f654e06538638aed8ccd71cbc78a15242ebbb - md5: 9d7e427d159c1b2d516cc047ff177c48 + requires_python: '>=3.10' +- conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-6.31.1-py312hb8af0ac_2.conda + sha256: 9c1dffa6371b5ae5a7659f08fa075a1a9d7b32fd11d5eaa1e7192eba4cae1207 + md5: 2aaf8d6c729beb30d1b41964e7fb2cd6 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 + - libabseil * cxx17* + - libabseil >=20250512.1,<20250513.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 constrains: - - libprotobuf 5.28.3 + - libprotobuf 6.31.1 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/protobuf?source=hash-mapping - size: 464794 - timestamp: 1731366525051 -- pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + size: 479025 + timestamp: 1760393393854 +- pypi: https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl name: psutil - version: 7.0.0 - sha256: 4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34 + version: 7.2.1 + sha256: 5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8 requires_dist: + - psleak ; extra == 'dev' - pytest ; extra == 'dev' + - pytest-instafail ; extra == 'dev' - pytest-xdist ; extra == 'dev' - setuptools ; extra == 'dev' - abi3audit ; extra == 'dev' - - black==24.10.0 ; extra == 'dev' + - black ; extra == 'dev' - check-manifest ; extra == 'dev' - coverage ; extra == 'dev' - packaging ; extra == 'dev' @@ -2435,10 +2589,13 @@ packages: - sphinx-rtd-theme ; extra == 'dev' - toml-sort ; extra == 'dev' - twine ; extra == 'dev' + - validate-pyproject[all] ; extra == 'dev' - virtualenv ; extra == 'dev' - vulture ; extra == 'dev' - wheel ; extra == 'dev' + - psleak ; extra == 'test' - pytest ; extra == 'test' + - pytest-instafail ; extra == 'test' - pytest-xdist ; extra == 'test' - setuptools ; extra == 'test' requires_python: '>=3.6' @@ -2453,53 +2610,70 @@ packages: purls: [] size: 8252 timestamp: 1726802366959 -- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda - sha256: 27f888492af3d5ab19553f263b0015bf3766a334668b5b3a79c7dc0416e603c1 - md5: 8088a5e7b2888c780738c3130f2a969d +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.1-pyh7a1b43c_0.conda + sha256: 2558727093f13d4c30e124724566d16badd7de532fd8ee7483628977117d02be + md5: 70ece62498c769280f791e836ac53fff depends: - - pybind11-global 2.13.6 *_2 + - python >=3.8 + - pybind11-global ==3.0.1 *_0 - python constrains: - - pybind11-abi ==4 + - pybind11-abi ==11 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/pybind11?source=hash-mapping - size: 186375 - timestamp: 1730237816231 -- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda - sha256: 9ff0d61d86878f81779bdb7e47656a75feaab539893462cff29b8ec353026d81 - md5: 120541563e520d12d8e39abd7de9092c + size: 232875 + timestamp: 1755953378112 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + sha256: 9e7fe12f727acd2787fb5816b2049cef4604b7a00ad3e408c5e709c298ce8bf1 + md5: f0599959a2447c1e544e216bddf393fa + license: BSD-3-Clause + license_family: BSD + purls: [] + size: 14671 + timestamp: 1752769938071 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-3.0.1-pyhc7ab6ef_0.conda + sha256: f11a5903879fe3a24e0d28329cb2b1945127e85a4cdb444b45545cf079f99e2d + md5: fe10b422ce8b5af5dab3740e4084c3f9 depends: + - python >=3.8 - __unix - python constrains: - - pybind11-abi ==4 + - pybind11-abi ==11 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/pybind11-global?source=hash-mapping - size: 179139 - timestamp: 1730237481227 -- pypi: https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl + size: 228871 + timestamp: 1755953338243 +- pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + name: pygments + version: 2.19.2 + sha256: 86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + requires_dist: + - colorama>=0.4.6 ; extra == 'windows-terminal' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl name: pyparsing - version: 3.2.3 - sha256: a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf + version: 3.3.2 + sha256: 850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d requires_dist: - railroad-diagrams ; extra == 'diagrams' - jinja2 ; extra == 'diagrams' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/80/be/ecb7cfb42d242b7ee764b52e6ff4782beeec00e3b943a3ec832b281f9da6/pyright-1.1.396-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl name: pyright - version: 1.1.396 - sha256: c635e473095b9138c471abccca22b9fedbe63858e0b40d4fc4b67da041891844 + version: 1.1.408 + sha256: 090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1 requires_dist: - nodeenv>=1.6.0 - typing-extensions>=4.1 - - twine>=3.4.1 ; extra == 'all' - - nodejs-wheel-binaries ; extra == 'all' - twine>=3.4.1 ; extra == 'dev' - nodejs-wheel-binaries ; extra == 'nodejs' + - twine>=3.4.1 ; extra == 'all' + - nodejs-wheel-binaries ; extra == 'all' requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/70/e3/8c4e0d24b46fbf02e6b2dc2da5d18f0c73cfd343a1fb01ae64c788c20e56/Pyro4-4.82-py2.py3-none-any.whl name: pyro4 @@ -2509,55 +2683,55 @@ packages: - serpent>=1.27,<1.30 ; python_full_version < '3.2' - serpent>=1.27 ; python_full_version >= '3.2' - selectors34 ; python_full_version < '3.4' -- pypi: https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl name: pytest - version: 8.3.5 - sha256: c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820 + version: 9.0.2 + sha256: 711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b requires_dist: - - colorama ; sys_platform == 'win32' - - exceptiongroup>=1.0.0rc8 ; python_full_version < '3.11' - - iniconfig - - packaging + - colorama>=0.4 ; sys_platform == 'win32' + - exceptiongroup>=1 ; python_full_version < '3.11' + - iniconfig>=1.0.1 + - packaging>=22 - pluggy>=1.5,<2 + - pygments>=2.7.2 - tomli>=1 ; python_full_version < '3.11' - argcomplete ; extra == 'dev' - attrs>=19.2 ; extra == 'dev' - hypothesis>=3.56 ; extra == 'dev' - mock ; extra == 'dev' - - pygments>=2.7.2 ; extra == 'dev' - requests ; extra == 'dev' - setuptools ; extra == 'dev' - xmlschema ; extra == 'dev' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl name: pytest-mock - version: 3.14.0 - sha256: 0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f + version: 3.15.1 + sha256: 0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d requires_dist: - pytest>=6.2.5 - pre-commit ; extra == 'dev' - pytest-asyncio ; extra == 'dev' - tox ; extra == 'dev' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.9-h9e4cc4f_1_cpython.conda + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.12-hd63d673_1_cpython.conda build_number: 1 - sha256: 77f2073889d4c91a57bc0da73a0466d9164dbcf6191ea9c3a7be6872f784d625 - md5: d82342192dfc9145185190e651065aa9 + sha256: 39898d24769a848c057ab861052e50bdc266310a7509efa3514b840e85a2ae98 + md5: 5c00c8cea14ee8d02941cab9121dce41 depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.6.4,<3.0a0 - - libffi >=3.4,<4.0a0 - - libgcc >=13 - - liblzma >=5.6.4,<6.0a0 + - libexpat >=2.7.1,<3.0a0 + - libffi >=3.5.2,<3.6.0a0 + - libgcc >=14 + - liblzma >=5.8.1,<6.0a0 - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.49.1,<4.0a0 - - libuuid >=2.38.1,<3.0a0 + - libsqlite >=3.50.4,<4.0a0 + - libuuid >=2.41.2,<3.0a0 - libxcrypt >=4.4.36 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.4.1,<4.0a0 + - openssl >=3.5.4,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata @@ -2565,8 +2739,8 @@ packages: - python_abi 3.12.* *_cp312 license: Python-2.0 purls: [] - size: 31670716 - timestamp: 1741130026152 + size: 31537229 + timestamp: 1761176876216 - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl name: python-dateutil version: 2.9.0.post0 @@ -2574,88 +2748,104 @@ packages: requires_dist: - six>=1.5 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda - build_number: 5 - sha256: d10e93d759931ffb6372b45d65ff34d95c6000c61a07e298d162a3bc2accebb0 - md5: 0424ae29b104430108f5218a66db7260 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda + build_number: 8 + sha256: 80677180dd3c22deb7426ca89d6203f1c7f1f256f2d5a94dc210f6e758229809 + md5: c3efd25ac4d74b1584d2f7a57195ddf1 constrains: - python 3.12.* *_cpython license: BSD-3-Clause license_family: BSD purls: [] - size: 6238 - timestamp: 1723823388266 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.6.0-cpu_mkl_py312_h446997d_101.conda - sha256: 6d1d48022c10b4da0355b3bb7886f391980351d0d7796925c1f853457bf172d8 - md5: 47d470ce7ceb3775d3f1a4c00ecef44d + size: 6958 + timestamp: 1752805918820 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.9.1-cpu_mkl_py312_h117a9d7_103.conda + sha256: b06e6dd81033c68f84145ed5d408ded04a9fe878d281e8fdea809d8639dedd29 + md5: 2dfd540359f08fe3a1e9a5a71657ad2a depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - filelock + - fmt >=12.1.0,<12.2.0a0 - fsspec - jinja2 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250512.1,<20250513.0a0 - libblas * *mkl - - libcblas >=3.9.0,<4.0a0 - - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 - - libstdcxx >=13 - - libtorch 2.6.0 cpu_mkl_hc5f969b_101 - - libuv >=1.50.0,<2.0a0 + - libcblas >=3.11.0,<4.0a0 + - libgcc >=14 + - libprotobuf >=6.31.1,<6.31.2.0a0 + - libstdcxx >=14 + - libtorch 2.9.1 cpu_mkl_hfee2a32_103 + - libuv >=1.51.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - llvm-openmp >=19.1.7 - - mkl >=2024.2.2,<2025.0a0 + - llvm-openmp >=21.1.8 + - mkl >=2025.3.0,<2026.0a0 - networkx - - numpy >=1.19,<3 + - numpy >=1.23,<3 - optree >=0.13.0 - pybind11 + - pybind11-abi 11 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - setuptools - - sleef >=3.8,<4.0a0 - - sympy >=1.13.1,!=1.13.2 + - sleef >=3.9.0,<4.0a0 + - sympy >=1.13.3 - typing_extensions >=4.10.0 constrains: - - pytorch-cpu ==2.6.0 - - pytorch-gpu ==99999999 + - pytorch-gpu <0.0a0 + - pytorch-cpu 2.9.1 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/torch?source=hash-mapping - size: 28335884 - timestamp: 1741568724835 -- pypi: https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + size: 25547087 + timestamp: 1768340770235 +- pypi: https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl name: pyyaml - version: 6.0.2 - sha256: 80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 + version: 6.0.3 + sha256: ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - sha256: d213c44958d49ce7e0d4d5b81afec23640cce5016685dbb2d23571a99caa4474 - md5: e84ddf12bde691e8ec894b00ea829ddf +- conda: https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.7.1-h8fae777_3.conda + sha256: 6e5e704c1c21f820d760e56082b276deaf2b53cf9b751772761c3088a365f6f4 + md5: 2c42649888aac645608191ffdc80d13a depends: - - libre2-11 2024.07.02 hbbce691_2 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - __glibc >=2.17 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 5176669 + timestamp: 1746622023242 +- conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_0.conda + sha256: 2f225ddf4a274743045aded48053af65c31721e797a45beed6774fdc783febfb + md5: 0227d04521bc3d28c7995c7e1f99a721 + depends: + - libre2-11 2025.11.05 h7b12aa8_0 license: BSD-3-Clause license_family: BSD purls: [] - size: 26786 - timestamp: 1735541074034 -- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c - md5: 283b96675859b20a825f8fa30f311446 + size: 27316 + timestamp: 1762397780316 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 + md5: d7d95fc8287ea7bf33e0e7116d2b95ec depends: - - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 - ncurses >=6.5,<7.0a0 license: GPL-3.0-only license_family: GPL purls: [] - size: 282480 - timestamp: 1740379431762 -- pypi: https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl + size: 345073 + timestamp: 1765813471974 +- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl name: requests - version: 2.32.3 - sha256: 70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 + version: 2.32.5 + sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 requires_dist: - charset-normalizer>=2,<4 - idna>=2.5,<4 @@ -2663,77 +2853,83 @@ packages: - certifi>=2017.4.17 - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/5e/a6/cc472161cd04d30a09d5c90698696b70c169eeba2c41030344194242db45/ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ruff - version: 0.9.10 - sha256: b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c + version: 0.14.14 + sha256: bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974 requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda - sha256: b9faaa024b77a3678a988c5a490f02c4029c0d5903998b585100e05bc7d4ff36 - md5: 00b999c5f9d01fb633db819d79186bd4 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.0-py312h54fa4ab_1.conda + sha256: 5b296faf6f5ff90d9ea3f6b16ff38fe2b8fe81c7c45b5e3a78b48887cca881d1 + md5: 828eb07c4c87c38ed8c6560c25893280 depends: - __glibc >=2.17,<3.0.a0 - libblas >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 - - libgcc >=13 + - libgcc >=14 - libgfortran - - libgfortran5 >=13.3.0 + - libgfortran5 >=14.3.0 - liblapack >=3.9.0,<4.0a0 - - libstdcxx >=13 - - numpy <2.5 - - numpy >=1.19,<3 - - numpy >=1.23.5 + - libstdcxx >=14 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=1.25.2 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=hash-mapping - size: 17064784 - timestamp: 1739791925628 -- pypi: https://files.pythonhosted.org/packages/ad/86/e029bd8a451f145e28530128239be057ca6e701aac46ad0d0000f852d551/serpent-1.41-py3-none-any.whl + - pkg:pypi/scipy?source=compressed-mapping + size: 16903519 + timestamp: 1768801007666 +- pypi: https://files.pythonhosted.org/packages/07/24/73031e6bd25d8f94811b3752b0b217efbdb20a67b65c6838c9af4e50c2e2/serpent-1.42-py3-none-any.whl name: serpent - version: '1.41' - sha256: 5fd776b3420441985bc10679564c2c9b4a19f77bea59f018e473441d98ae5dd7 + version: '1.42' + sha256: a02f5a4fcf3b41ee6204b36c3cf026bf0433ffe15b6a7fc8a37e0bff74d87575 requires_python: '>=3.2' -- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - sha256: 91d664ace7c22e787775069418daa9f232ee8bafdd0a6a080a5ed2395a6fa6b2 - md5: 9bddfdbf4e061821a1a443f93223be61 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.10.1-pyh332efcf_0.conda + sha256: 89d5bb48047e7e27aa52a3a71d6ebf386e5ee4bdbd7ca91d653df9977eca8253 + md5: cb72cedd94dd923c6a9405a3d3b1c018 depends: - - python >=3.9 + - python >=3.10 license: MIT license_family: MIT purls: - pkg:pypi/setuptools?source=compressed-mapping - size: 777736 - timestamp: 1740654030775 -- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - sha256: 41db0180680cc67c3fa76544ffd48d6a5679d96f4b71d7498a759e94edc9a2db - md5: a451d576819089b0d672f18768be0f65 - depends: - - python >=3.9 - license: MIT - license_family: MIT - purls: - - pkg:pypi/six?source=hash-mapping - size: 16385 - timestamp: 1733381032766 -- conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda - sha256: c998d5a29848ce9ff1c53ba506e7d01bbd520c39bbe72e2fb7cdf5a53bad012f - md5: aec4dba5d4c2924730088753f6fa164b + size: 678025 + timestamp: 1768998156365 +- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + name: six + version: 1.17.0 + sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + sha256: 57afc2ab5bdb24cf979964018dddbc5dfaee130b415e6863765e45aed2175ee4 + md5: e8a0b4f5e82ecacffaa5e805020473cb depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 - - libgcc >=13 - - libstdcxx >=13 + - libgcc >=14 + - libstdcxx >=14 license: BSL-1.0 purls: [] - size: 1920152 - timestamp: 1738089391074 -- conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - sha256: 929d939c5a8bcdc10a17501890918da68cf14a5883b36fddf77b8f0fbf040be2 - md5: 254cd5083ffa04d96e3173397a3d30f4 + size: 1951720 + timestamp: 1756274576844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-3.1.2-hecca717_0.conda + sha256: 34e2e9c505cd25dba0a9311eb332381b15147cf599d972322a7c197aedfc8ce2 + md5: 9859766c658e78fec9afa4a54891d920 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + size: 2741200 + timestamp: 1756086702093 +- conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda + sha256: 09d3b6ac51d437bc996ad006d9f749ca5c645c1900a854a6c8f193cbd13f03a8 + md5: 8c09fac3785696e1c477156192d64b91 depends: - __unix - cpython @@ -2744,49 +2940,50 @@ packages: license_family: BSD purls: - pkg:pypi/sympy?source=hash-mapping - size: 4523617 - timestamp: 1736248315124 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - sha256: 65463732129899770d54b1fbf30e1bb82fdebda9d7553caf08d23db4590cd691 - md5: ba7726b8df7b9d34ea80e82b097a4893 + size: 4616621 + timestamp: 1745946173026 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2022.3.0-hb700be7_2.conda + sha256: 975710e4b7f1b13c3c30b7fbf21e22f50abe0463b6b47a231582fdedcc45c961 + md5: 8f7278ca5f7456a974992a8b34284737 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libhwloc >=2.11.2,<2.11.3.0a0 - - libstdcxx >=13 + - libgcc >=14 + - libhwloc >=2.12.2,<2.12.3.0a0 + - libstdcxx >=14 license: Apache-2.0 license_family: APACHE purls: [] - size: 175954 - timestamp: 1732982638805 -- conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.19.0-pyhd8ed1ab_0.conda - sha256: 348a60f31eb14fd2e10f00d3c7b5036981b9b2d957cf56b1c9fecbfa6c0782d5 - md5: 59cc119240f48dc05369fa8bea07ea05 + size: 181329 + timestamp: 1767886632911 +- conda: https://conda.anaconda.org/conda-forge/noarch/tensorboard-2.20.0-pyhe01879c_0.conda + sha256: 7856949642db7b5d939622bd1702e365bf2cfc434e2e07f0ee88e5cacfd1a2f5 + md5: 37f7666c348f9f09378c3aefa90cb434 depends: + - python >=3.9 - absl-py >=0.4 - grpcio >=1.48.2 - markdown >=2.6.8 - numpy >=1.12.0 - packaging + - pillow - protobuf >=3.19.6,!=4.24.0 - - python >=3.9 + - werkzeug >=1.0.1 - setuptools >=41.0.0 - - six >1.9 - tensorboard-data-server >=0.7.0,<0.8.0 - - werkzeug >=1.0.1 + - python license: Apache-2.0 license_family: APACHE purls: - pkg:pypi/tensorboard?source=hash-mapping - size: 5188674 - timestamp: 1740522550333 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312hda17c39_2.conda - sha256: 9dc3ebf51a96a1c2b82b2d8907030453b797141a8cb0bdaa66c67c7b622d28c3 - md5: 8f4e72393be23b3eaa7bbc3cd4a71c40 + size: 5250436 + timestamp: 1752825441697 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tensorboard-data-server-0.7.0-py312h4eba8b5_4.conda + sha256: 9a3faf2dd703667683feee3cb0ecb9ed035b0a5193b95da3a633ff9da029fea9 + md5: 7d6b10ed9058f6a97cd8007605b61c33 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - openssl >=3.3.2,<4.0a0 + - libgcc >=14 + - openssl >=3.5.4,<4.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 constrains: @@ -2795,55 +2992,80 @@ packages: license_family: APACHE purls: - pkg:pypi/tensorboard-data-server?source=hash-mapping - size: 3571270 - timestamp: 1728640174186 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e - md5: d453b98d9c83e71da0741bb0ff4d76bc + size: 3491485 + timestamp: 1764929907168 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + sha256: 1544760538a40bcd8ace2b1d8ebe3eb5807ac268641f8acdc18c69c5ebfeaf64 + md5: 86bc20552bf46075e3d92b67f089172d depends: - - libgcc-ng >=12 - - libzlib >=1.2.13,<2.0.0a0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + constrains: + - xorg-libx11 >=1.8.12,<2.0a0 license: TCL license_family: BSD purls: [] - size: 3318875 - timestamp: 1699202167581 -- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - sha256: 18636339a79656962723077df9a56c0ac7b8a864329eb8f847ee3d38495b863e - md5: ac944244f1fed2eb49bae07193ae8215 + size: 3284905 + timestamp: 1763054914403 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.0-pyhcf101f3_0.conda + sha256: 62940c563de45790ba0f076b9f2085a842a65662268b02dd136a8e9b1eaf47a8 + md5: 72e780e9aa2d0a3295f59b1874e3768b depends: - - python >=3.9 + - python >=3.10 + - python license: MIT license_family: MIT purls: - - pkg:pypi/tomli?source=hash-mapping - size: 19167 - timestamp: 1733256819729 -- conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.21.0-cpu_py312_h11dcf35_0.conda - sha256: a4ca6214fa63e97567fba8df5f03ff2a32fceca67f75c3b0fb55c11dd5b215b7 - md5: 2498c29ec4e9742141697fcda2cdf9f1 + - pkg:pypi/tomli?source=compressed-mapping + size: 21453 + timestamp: 1768146676791 +- conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-0.25.0-cpu_py312_hd7553a8_0.conda + sha256: 90021d9023604e1f5be1f56eab74a8b418d73dc758407e9bdbee3f60d01ca83a + md5: fb6477ab9e5a0a319f4bf53b339ec3aa depends: - python - pytorch * cpu* - pillow >=5.3.0,!=8.3.0,!=8.3.1 - - numpy >=1.23.5 - - libgcc >=13 - - libstdcxx >=13 - - libgcc >=13 + - torchvision-extra-decoders - __glibc >=2.17,<3.0.a0 - - python_abi 3.12.* *_cp312 - - libwebp-base >=1.5.0,<2.0a0 + - libstdcxx >=14 + - libgcc >=14 + - libjpeg-turbo >=3.1.2,<4.0a0 + - pytorch >=2.9.1,<2.10.0a0 + - libtorch >=2.9.1,<2.10.0a0 - giflib >=5.2.2,<5.3.0a0 - - libpng >=1.6.46,<1.7.0a0 - - libjpeg-turbo >=3.0.0,<4.0a0 - - pytorch >=2.6.0,<2.7.0a0 - - libtorch >=2.6.0,<2.7.0a0 + - libpng >=1.6.54,<1.7.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - numpy >=1.23,<3 + - python_abi 3.12.* *_cp312 + constrains: + - numpy <2.4 license: BSD-3-Clause - license_family: BSD purls: - pkg:pypi/torchvision?source=hash-mapping - size: 1434917 - timestamp: 1739668842759 + size: 1503867 + timestamp: 1769244527863 +- conda: https://conda.anaconda.org/conda-forge/linux-64/torchvision-extra-decoders-0.0.2-py312h49ceefc_5.conda + sha256: 492c3b045b512417244b1ad91927d5361fe44d28a2189a14a86580fbfb399bf5 + md5: 8034b3e1418a82b97b30ca41477f7f22 + depends: + - python + - libgcc >=14 + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - pytorch >=2.9.1,<2.10.0a0 + - libtorch >=2.9.1,<2.10.0a0 + - python_abi 3.12.* *_cp312 + - libavif16 >=1.3.0,<2.0a0 + - libheif >=1.19.7,<1.20.0a0 + - libtorch >=2.9.1,<2.10.0a0 + license: LGPL-2.1-only + purls: + - pkg:pypi/torchvision-extra-decoders?source=hash-mapping + size: 67409 + timestamp: 1764215676340 - pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl name: tqdm version: 4.67.1 @@ -2860,51 +3082,51 @@ packages: - requests ; extra == 'telegram' - ipywidgets>=6 ; extra == 'notebook' requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda - sha256: 8cd43b561122bfeb7e99df2dc3ec5633d5888e54fa07c059d993a5971b3f3a94 - md5: 810ef4243f6d79c0b8053f21fbee2101 +- conda: https://conda.anaconda.org/conda-forge/noarch/trove-classifiers-2026.1.14.14-pyhd8ed1ab_0.conda + sha256: 302d576f7e44fa13d2849b901772a04f1c2aabc5d6b6c7dcdc5a271bcffd50fe + md5: f5793a97363a42fd6a98f31f29537bbc depends: - - python >=3.9 + - python >=3.10 license: Apache-2.0 license_family: Apache purls: - pkg:pypi/trove-classifiers?source=hash-mapping - size: 18705 - timestamp: 1741073502142 -- pypi: https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl + size: 19707 + timestamp: 1768550221435 +- pypi: https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl name: types-pyyaml - version: 6.0.12.20241230 - sha256: fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6 - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_1.conda - noarch: python - sha256: c8e9c1c467b5f960b627d7adc1c65fece8e929a3de89967e91ef0f726422fd32 - md5: b6a408c64b78ec7b779a3e5c7a902433 + version: 6.0.12.20250915 + sha256: e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6 + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 depends: - - typing_extensions 4.12.2 pyha770c72_1 + - typing_extensions ==4.15.0 pyhcf101f3_0 license: PSF-2.0 license_family: PSF purls: [] - size: 10075 - timestamp: 1733188758872 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - sha256: 337be7af5af8b2817f115b3b68870208b30c31d3439bec07bfb2d8f4823e3568 - md5: d17f13df8b65464ca316cbc000a3cb64 + size: 91383 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 + md5: 0caa1af407ecff61170c9437a808404d depends: - - python >=3.9 + - python >=3.10 + - python license: PSF-2.0 license_family: PSF purls: - pkg:pypi/typing-extensions?source=hash-mapping - size: 39637 - timestamp: 1733188758212 -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - sha256: c4b1ae8a2931fe9b274c44af29c5475a85b37693999f8c792dad0f8c6734b1de - md5: dbcace4706afdfb7eb891f7b37d07c04 + size: 51692 + timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c + md5: ad659d0a2b3e47e38d829aa8cad2d610 license: LicenseRef-Public-Domain purls: [] - size: 122921 - timestamp: 1737119101255 + size: 119135 + timestamp: 1767016325805 - pypi: https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl name: update-checker version: 0.18.0 @@ -2917,26 +3139,28 @@ packages: - black ; extra == 'lint' - flake8 ; extra == 'lint' - pytest>=2.7.3 ; extra == 'test' -- pypi: https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl name: urllib3 - version: 2.3.0 - sha256: 1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df + version: 2.6.3 + sha256: bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 requires_dist: - - brotli>=1.0.9 ; platform_python_implementation == 'CPython' and extra == 'brotli' - - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' + - brotli>=1.2.0 ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi>=1.2.0.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' - h2>=4,<5 ; extra == 'h2' - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' - - zstandard>=0.18.0 ; extra == 'zstd' + - backports-zstd>=1.0.0 ; python_full_version < '3.14' and extra == 'zstd' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/c2/eb/c6db6e3001d58c6a9e67c74bb7b4206767caa3ccc28c6b9eaf4c23fb4e34/virtualenv-20.29.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl name: virtualenv - version: 20.29.3 - sha256: 3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170 + version: 20.36.1 + sha256: 575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f requires_dist: - distlib>=0.3.7,<1 - - filelock>=3.12.2,<4 + - filelock>=3.16.1,<4 ; python_full_version < '3.10' + - filelock>=3.20.1,<4 ; python_full_version >= '3.10' - importlib-metadata>=6.6 ; python_full_version < '3.8' - platformdirs>=3.9.1,<5 + - typing-extensions>=4.13.2 ; python_full_version < '3.11' - furo>=2023.7.26 ; extra == 'docs' - proselint>=0.13 ; extra == 'docs' - sphinx>=7.1.2,!=7.3 ; extra == 'docs' @@ -2949,7 +3173,7 @@ packages: - flaky>=3.7 ; extra == 'test' - packaging>=23.1 ; extra == 'test' - pytest-env>=0.8.2 ; extra == 'test' - - pytest-freezer>=0.4.8 ; (python_full_version >= '3.13' and platform_python_implementation == 'CPython' and sys_platform == 'win32' and extra == 'test') or (platform_python_implementation == 'PyPy' and extra == 'test') + - pytest-freezer>=0.4.8 ; (python_full_version >= '3.13' and platform_python_implementation == 'CPython' and sys_platform == 'win32' and extra == 'test') or (platform_python_implementation == 'GraalVM' and extra == 'test') or (platform_python_implementation == 'PyPy' and extra == 'test') - pytest-mock>=3.11.1 ; extra == 'test' - pytest-randomly>=3.12 ; extra == 'test' - pytest-timeout>=2.1 ; extra == 'test' @@ -2957,39 +3181,55 @@ packages: - setuptools>=68 ; extra == 'test' - time-machine>=2.10 ; platform_python_implementation == 'CPython' and extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl name: websocket-client - version: 1.8.0 - sha256: 17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526 + version: 1.9.0 + sha256: af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef requires_dist: + - pytest ; extra == 'test' + - websockets ; extra == 'test' + - python-socks ; extra == 'optional' + - wsaccel ; extra == 'optional' - sphinx>=6.0 ; extra == 'docs' - sphinx-rtd-theme>=1.1.0 ; extra == 'docs' - myst-parser>=2.0.0 ; extra == 'docs' - - python-socks ; extra == 'optional' - - wsaccel ; extra == 'optional' - - websockets ; extra == 'test' - requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.3-pyhd8ed1ab_1.conda - sha256: cd9a603beae0b237be7d9dfae8ae0b36ad62666ac4bb073969bce7da6f55157c - md5: 0a9b57c159d56b508613cc39022c1b9e + requires_python: '>=3.9' +- conda: https://conda.anaconda.org/conda-forge/noarch/werkzeug-3.1.5-pyhcf101f3_0.conda + sha256: 3ef418943ef14939a4bbc5157f31db2d6a7a025a3bfd7b4aa5a29034ba96e42e + md5: 784e86b857b809955635175881a9a418 depends: - markupsafe >=2.1.1 - - python >=3.9 + - python >=3.10 + - python license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/werkzeug?source=hash-mapping - size: 243546 - timestamp: 1733160561258 + - pkg:pypi/werkzeug?source=compressed-mapping + size: 257059 + timestamp: 1767946313110 - pypi: https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip name: wget version: '3.2' sha256: 35e630eca2aa50ce998b9b1a127bb26b30dfee573702782aa982f875e3f16061 -- pypi: https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl +- conda: https://conda.anaconda.org/conda-forge/linux-64/x265-3.5-h924138e_3.tar.bz2 + sha256: 76c7405bcf2af639971150f342550484efac18219c0203c5ee2e38b8956fe2a0 + md5: e7f6ed84d4623d52ee581325c1587a6b + depends: + - libgcc-ng >=10.3.0 + - libstdcxx-ng >=10.3.0 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + size: 3357188 + timestamp: 1646609687141 +- pypi: https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl name: xmltodict - version: 0.14.2 - sha256: 20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac - requires_python: '>=3.6' + version: 1.0.2 + sha256: 62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d + requires_dist: + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda sha256: c12396aabb21244c212e488bbdc4abcdef0b7404b15761d9329f5a4a39113c4b md5: fb901ff28063514abb6046c9ec2c4a45 @@ -3026,28 +3266,28 @@ packages: purls: [] size: 835896 timestamp: 1741901112627 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - sha256: ed10c9283974d311855ae08a16dfd7e56241fac632aec3b92e3cfe73cff31038 - md5: f6ebe2cb3f82ba6c057dde5d9debe4f7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b + md5: b2895afaf55bf96a8c8282a2e47a5de0 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 14780 - timestamp: 1734229004433 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda - sha256: 6b250f3e59db07c2514057944a3ea2044d6a8cdde8a47b6497c254520fade1ee - md5: 8035c64cb77ed555e3f150b7b3972480 + size: 15321 + timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 + md5: 1dafce8548e38671bea82e3f5c6ce22f depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 + - libgcc >=14 license: MIT license_family: MIT purls: [] - size: 19901 - timestamp: 1727794976192 + size: 20591 + timestamp: 1762976546182 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda sha256: da5dc921c017c05f38a38bd75245017463104457b63a1ce633ed41f214159c14 md5: febbab7d15033c913d53c7a2c102309d @@ -3060,18 +3300,18 @@ packages: purls: [] size: 50060 timestamp: 1727752228921 -- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.1-hb9d3cd8_0.conda - sha256: 2fef37e660985794617716eb915865ce157004a4d567ed35ec16514960ae9271 - md5: 4bdb303603e9821baf5fe5fdff1dc8f8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda + sha256: 83c4c99d60b8784a611351220452a0a85b080668188dce5dfa394b723d7b64f4 + md5: ba231da7fccf9ea1e768caf5c7099b84 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - xorg-libx11 >=1.8.10,<2.0a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 license: MIT license_family: MIT purls: [] - size: 19575 - timestamp: 1727794961233 + size: 20071 + timestamp: 1759282564045 - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.2-hb9d3cd8_0.conda sha256: 1a724b47d98d7880f26da40e45f01728e7638e6ec69f35a3e11f92acd05f9e7a md5: 17dcc85db3c7886650b8908b183d6876 @@ -3140,27 +3380,38 @@ packages: purls: [] size: 32808 timestamp: 1727964811275 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - sha256: 567c04f124525c97a096b65769834b7acb047db24b15a56888a322bf3966c3e1 - md5: 0c3cc595284c5e8f0f9900a9b228a332 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae + md5: 30cd29cb87d819caead4d55184c1d115 depends: - - python >=3.9 + - python >=3.10 + - python license: MIT license_family: MIT purls: - pkg:pypi/zipp?source=hash-mapping - size: 21809 - timestamp: 1732827613585 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda - sha256: 532d3623961e34c53aba98db2ad0a33b7a52ff90d6960e505fb2d2efc06bb7da - md5: 02e4e2fa41a6528afba2e54cbc4280ff + size: 24194 + timestamp: 1764460141901 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.2-hceb46e0_1.conda + sha256: f2b6a175677701a0b6ce556b3bd362dc94a4e36ffcd10e3860e52ca036b4ad96 + md5: 40feea2979654ed579f1cda7c63ccb94 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Zlib + license_family: Other + purls: [] + size: 122303 + timestamp: 1766076745735 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 + md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 567419 - timestamp: 1740255350233 + size: 601375 + timestamp: 1764777111296 diff --git a/pyproject.toml b/pyproject.toml index b0b1b57..7017b92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "torchvision", "tqdm", "tensorboard", + "pyright", ] [tool.hatch.build.targets.wheel] @@ -45,7 +46,6 @@ mineagent = "mineagent.engine:run" [project.optional-dependencies] dev = [ "pre-commit", - "pyright", "pytest", "pytest-mock", "ruff", @@ -85,8 +85,11 @@ py312 = { features = ["py312"] } [tool.pixi.feature.py312.dependencies] python = "3.12.*" +[tool.pixi.feature.dev.dependencies] +nodejs = ">=18" + [tool.pixi.tasks] mineagent = "mineagent" gradle-run-client = "cd forge && gradle runClient" gradle-build = "cd forge && gradle build" -gradle-test = "cd forge && gradle test" \ No newline at end of file +gradle-test = "cd forge && gradle test" From 0add1216513f892a2742d1521f0b3a5ee7f2562b Mon Sep 17 00:00:00 2001 From: thomashopkins32 Date: Sun, 25 Jan 2026 16:42:34 -0500 Subject: [PATCH 19/21] Move test_action_client.py --- test_action_client.py => scripts/test_action_client.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test_action_client.py => scripts/test_action_client.py (100%) diff --git a/test_action_client.py b/scripts/test_action_client.py similarity index 100% rename from test_action_client.py rename to scripts/test_action_client.py From d325b22ac6ffa095396caa6f9e19ef9360f68275 Mon Sep 17 00:00:00 2001 From: thomashopkins32 Date: Sun, 25 Jan 2026 16:43:35 -0500 Subject: [PATCH 20/21] Change minecraft-build -> gradle-build in workflow --- .github/workflows/gradle-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml index 640d33a..8701beb 100644 --- a/.github/workflows/gradle-build.yml +++ b/.github/workflows/gradle-build.yml @@ -25,4 +25,4 @@ jobs: - name: Build with Gradle run: | cd forge - pixi run minecraft-build + pixi run gradle-build From dcad662cc66b6c85b8d296ce625ecb7d36b2074e Mon Sep 17 00:00:00 2001 From: thomashopkins32 Date: Sun, 25 Jan 2026 16:48:54 -0500 Subject: [PATCH 21/21] Update dependencies and apply spotless --- .../com/mineagent/ClientEventHandler.java | 170 +++-- forge/src/main/java/com/mineagent/Config.java | 3 +- .../main/java/com/mineagent/DataBridge.java | 140 ++-- .../java/com/mineagent/InputInjector.java | 639 ++++++++--------- .../java/com/mineagent/NetworkHandler.java | 672 +++++++++--------- .../main/java/com/mineagent/Observation.java | 11 +- .../src/main/java/com/mineagent/RawInput.java | 57 +- .../java/com/mineagent/InputInjectorTest.java | 19 +- .../com/mineagent/MinecraftTestFixture.java | 56 +- .../test/java/com/mineagent/RawInputTest.java | 494 ++++++------- pixi.lock | 20 +- pyproject.toml | 2 +- 12 files changed, 1111 insertions(+), 1172 deletions(-) diff --git a/forge/src/main/java/com/mineagent/ClientEventHandler.java b/forge/src/main/java/com/mineagent/ClientEventHandler.java index fa6327b..81ce7be 100644 --- a/forge/src/main/java/com/mineagent/ClientEventHandler.java +++ b/forge/src/main/java/com/mineagent/ClientEventHandler.java @@ -14,101 +14,97 @@ import org.lwjgl.opengl.GL11; import org.slf4j.Logger; -/** - * Handles client-side game events and coordinates input injection with observations. - */ +/** Handles client-side game events and coordinates input injection with observations. */ public class ClientEventHandler { - private static final Logger LOGGER = LogUtils.getLogger(); - private static final DataBridge dataBridge = DataBridge.getInstance(); - - @SubscribeEvent - public static void onServerStarting(ServerStartingEvent event) { - LOGGER.info("MineAgent Mod Server Starting - Network handler is managed on client side"); - } + private static final Logger LOGGER = LogUtils.getLogger(); + private static final DataBridge dataBridge = DataBridge.getInstance(); - @SubscribeEvent - public static void onServerStopping(ServerStoppingEvent event) { - LOGGER.info("MineAgent Mod Server Stopping"); - } + @SubscribeEvent + public static void onServerStarting(ServerStartingEvent event) { + LOGGER.info("MineAgent Mod Server Starting - Network handler is managed on client side"); + } - /** - * Main game tick handler. Processes raw input and captures observations. - */ - @SubscribeEvent - public static void onClientTick(TickEvent.ClientTickEvent event) { - Minecraft mc = Minecraft.getInstance(); - if (mc.level != null && mc.player != null && event.phase == TickEvent.Phase.END) { - // Handle input suppression when client is connected - handleInputSuppression(mc); - - // Process any pending raw input - final RawInput rawInput = dataBridge.getLatestRawInput(); - if (rawInput != null) { - dataBridge.getInputInjector().inject(rawInput); - } - - // IMPORTANT: Maintain button state every tick for continuous actions - // This fires press events and sets KeyMapping states for held buttons - dataBridge.getInputInjector().maintainButtonState(); - - // Capture and send observation - final byte[] frame = captureFrame(); - dataBridge.sendObservation(new Observation(0.0, frame)); - } - } - - /** - * Handles input suppression when a Python client is connected. - * Disables the system cursor to prevent real mouse input from interfering. - */ - private static void handleInputSuppression(Minecraft mc) { - boolean clientConnected = dataBridge.isClientConnected(); - boolean suppressMouse = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); - boolean suppressKeyboard = Config.SUPPRESS_SYSTEM_KEYBOARD_INPUT.get(); - - if (clientConnected && (suppressMouse || suppressKeyboard)) { - long windowHandle = mc.getWindow().getWindow(); - - // Suppress mouse by hiding/disabling cursor - if (suppressMouse) { - GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - - // Note: Keyboard suppression would require intercepting at a lower level - // For now, the agent's input will override via the GLFW handlers - } - } + @SubscribeEvent + public static void onServerStopping(ServerStoppingEvent event) { + LOGGER.info("MineAgent Mod Server Stopping"); + } - @SubscribeEvent - public static void onPlayerHurt(LivingHurtEvent event) { - // Future: Calculate reward based on damage - // if (event.getEntity() instanceof Player) { - // dataBridge.sendEvent("PLAYER_HURT", String.valueOf(event.getAmount())); - // } - } + /** Main game tick handler. Processes raw input and captures observations. */ + @SubscribeEvent + public static void onClientTick(TickEvent.ClientTickEvent event) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level != null && mc.player != null && event.phase == TickEvent.Phase.END) { + // Handle input suppression when client is connected + handleInputSuppression(mc); - @SubscribeEvent - public static void onPlayerDeath(LivingDeathEvent event) { - // Future: Calculate negative reward on death - // if (event.getEntity() instanceof Player) { - // dataBridge.sendEvent("PLAYER_DEATH", "-100.0"); - // } - } - - private static byte[] captureFrame() { - Minecraft mc = Minecraft.getInstance(); - return captureScreenshot(mc.getWindow()); + // Process any pending raw input + final RawInput rawInput = dataBridge.getLatestRawInput(); + if (rawInput != null) { + dataBridge.getInputInjector().inject(rawInput); + } + + // IMPORTANT: Maintain button state every tick for continuous actions + // This fires press events and sets KeyMapping states for held buttons + dataBridge.getInputInjector().maintainButtonState(); + + // Capture and send observation + final byte[] frame = captureFrame(); + dataBridge.sendObservation(new Observation(0.0, frame)); } + } - private static byte[] captureScreenshot(Window window) { - int width = window.getWidth(); - int height = window.getHeight(); + /** + * Handles input suppression when a Python client is connected. Disables the system cursor to + * prevent real mouse input from interfering. + */ + private static void handleInputSuppression(Minecraft mc) { + boolean clientConnected = dataBridge.isClientConnected(); + boolean suppressMouse = Config.SUPPRESS_SYSTEM_MOUSE_INPUT.get(); + boolean suppressKeyboard = Config.SUPPRESS_SYSTEM_KEYBOARD_INPUT.get(); - ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 3); - GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + if (clientConnected && (suppressMouse || suppressKeyboard)) { + long windowHandle = mc.getWindow().getWindow(); - byte[] bytes = new byte[buffer.capacity()]; - buffer.get(bytes); - return bytes; + // Suppress mouse by hiding/disabling cursor + if (suppressMouse) { + GLFW.glfwSetInputMode(windowHandle, GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } + + // Note: Keyboard suppression would require intercepting at a lower level + // For now, the agent's input will override via the GLFW handlers } + } + + @SubscribeEvent + public static void onPlayerHurt(LivingHurtEvent event) { + // Future: Calculate reward based on damage + // if (event.getEntity() instanceof Player) { + // dataBridge.sendEvent("PLAYER_HURT", String.valueOf(event.getAmount())); + // } + } + + @SubscribeEvent + public static void onPlayerDeath(LivingDeathEvent event) { + // Future: Calculate negative reward on death + // if (event.getEntity() instanceof Player) { + // dataBridge.sendEvent("PLAYER_DEATH", "-100.0"); + // } + } + + private static byte[] captureFrame() { + Minecraft mc = Minecraft.getInstance(); + return captureScreenshot(mc.getWindow()); + } + + private static byte[] captureScreenshot(Window window) { + int width = window.getWidth(); + int height = window.getHeight(); + + ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 3); + GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer); + + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(bytes); + return bytes; + } } diff --git a/forge/src/main/java/com/mineagent/Config.java b/forge/src/main/java/com/mineagent/Config.java index d8002e4..e64e263 100644 --- a/forge/src/main/java/com/mineagent/Config.java +++ b/forge/src/main/java/com/mineagent/Config.java @@ -81,7 +81,8 @@ public class Config { SUPPRESS_SYSTEM_MOUSE_INPUT = BUILDER - .comment("If true, disables OS cursor when Python client is connected for agent mouse control") + .comment( + "If true, disables OS cursor when Python client is connected for agent mouse control") .define("suppress_system_mouse_input", true); SUPPRESS_SYSTEM_KEYBOARD_INPUT = diff --git a/forge/src/main/java/com/mineagent/DataBridge.java b/forge/src/main/java/com/mineagent/DataBridge.java index f01a2f7..af8726d 100644 --- a/forge/src/main/java/com/mineagent/DataBridge.java +++ b/forge/src/main/java/com/mineagent/DataBridge.java @@ -6,91 +6,77 @@ import org.slf4j.Logger; /** - * Central bridge for data exchange between network handler and game events. - * Manages the latest raw input, observations, and connection state. + * Central bridge for data exchange between network handler and game events. Manages the latest raw + * input, observations, and connection state. */ public class DataBridge { - private static final Logger LOGGER = LogUtils.getLogger(); - private static DataBridge instance; - - private NetworkHandler networkHandler; - - // Raw input handling - private final AtomicReference latestRawInput = new AtomicReference<>(); - - // Input injection - private final InputInjector inputInjector = new InputInjector(); - - // Connection state for input suppression - private final AtomicBoolean clientConnected = new AtomicBoolean(false); - - private DataBridge() {} + private static final Logger LOGGER = LogUtils.getLogger(); + private static DataBridge instance; - public static synchronized DataBridge getInstance() { - if (instance == null) { - instance = new DataBridge(); - LOGGER.info("DataBridge instance created"); - } - return instance; - } + private NetworkHandler networkHandler; - public void setNetworkHandler(NetworkHandler handler) { - this.networkHandler = handler; - LOGGER.info("NetworkHandler connected to DataBridge"); - } + // Raw input handling + private final AtomicReference latestRawInput = new AtomicReference<>(); - /** - * Sends an observation to connected clients. - */ - public void sendObservation(Observation obs) { - if (networkHandler != null) { - networkHandler.setLatest(obs.frame(), obs.reward()); - } else { - LOGGER.warn("Cannot send frame - NetworkHandler is null"); - } - } - - /** - * Sets the latest raw input received from the Python agent. - */ - public void setLatestRawInput(RawInput rawInput) { - latestRawInput.set(rawInput); - } + // Input injection + private final InputInjector inputInjector = new InputInjector(); - /** - * Gets and clears the latest raw input. - * Returns null if no new input is available. - */ - public RawInput getLatestRawInput() { - RawInput rawInput = latestRawInput.getAndSet(null); - if (rawInput != null) { - LOGGER.debug("DataBridge getting latest raw input: {} keys", rawInput.keyCodes().length); - } - return rawInput; + // Connection state for input suppression + private final AtomicBoolean clientConnected = new AtomicBoolean(false); + + private DataBridge() {} + + public static synchronized DataBridge getInstance() { + if (instance == null) { + instance = new DataBridge(); + LOGGER.info("DataBridge instance created"); } - - /** - * Gets the input injector for injecting raw input into Minecraft. - */ - public InputInjector getInputInjector() { - return inputInjector; + return instance; + } + + public void setNetworkHandler(NetworkHandler handler) { + this.networkHandler = handler; + LOGGER.info("NetworkHandler connected to DataBridge"); + } + + /** Sends an observation to connected clients. */ + public void sendObservation(Observation obs) { + if (networkHandler != null) { + networkHandler.setLatest(obs.frame(), obs.reward()); + } else { + LOGGER.warn("Cannot send frame - NetworkHandler is null"); } - - /** - * Sets whether a Python client is connected. - * Used for input suppression. - */ - public void setClientConnected(boolean connected) { - boolean wasConnected = clientConnected.getAndSet(connected); - if (wasConnected != connected) { - LOGGER.info("Client connection state changed: {}", connected ? "CONNECTED" : "DISCONNECTED"); - } + } + + /** Sets the latest raw input received from the Python agent. */ + public void setLatestRawInput(RawInput rawInput) { + latestRawInput.set(rawInput); + } + + /** Gets and clears the latest raw input. Returns null if no new input is available. */ + public RawInput getLatestRawInput() { + RawInput rawInput = latestRawInput.getAndSet(null); + if (rawInput != null) { + LOGGER.debug("DataBridge getting latest raw input: {} keys", rawInput.keyCodes().length); } - - /** - * Returns whether a Python client is currently connected. - */ - public boolean isClientConnected() { - return clientConnected.get(); + return rawInput; + } + + /** Gets the input injector for injecting raw input into Minecraft. */ + public InputInjector getInputInjector() { + return inputInjector; + } + + /** Sets whether a Python client is connected. Used for input suppression. */ + public void setClientConnected(boolean connected) { + boolean wasConnected = clientConnected.getAndSet(connected); + if (wasConnected != connected) { + LOGGER.info("Client connection state changed: {}", connected ? "CONNECTED" : "DISCONNECTED"); } + } + + /** Returns whether a Python client is currently connected. */ + public boolean isClientConnected() { + return clientConnected.get(); + } } diff --git a/forge/src/main/java/com/mineagent/InputInjector.java b/forge/src/main/java/com/mineagent/InputInjector.java index 0c2393d..44fc5d6 100644 --- a/forge/src/main/java/com/mineagent/InputInjector.java +++ b/forge/src/main/java/com/mineagent/InputInjector.java @@ -11,354 +11,337 @@ import org.slf4j.Logger; /** - * Injects raw input events directly into Minecraft's GLFW callback handlers. - * This provides a unified architecture where both keyboard and mouse input - * go through the same handlers that real hardware input uses. + * Injects raw input events directly into Minecraft's GLFW callback handlers. This provides a + * unified architecture where both keyboard and mouse input go through the same handlers that real + * hardware input uses. */ public class InputInjector { - private static final Logger LOGGER = LogUtils.getLogger(); - - // Modifier keys - private static final Set MODIFIER_KEYS = new HashSet<>(Arrays.asList( - GLFW.GLFW_KEY_LEFT_SHIFT, - GLFW.GLFW_KEY_RIGHT_SHIFT, - GLFW.GLFW_KEY_LEFT_CONTROL, - GLFW.GLFW_KEY_RIGHT_CONTROL, - GLFW.GLFW_KEY_LEFT_ALT, - GLFW.GLFW_KEY_RIGHT_ALT, - GLFW.GLFW_KEY_LEFT_SUPER, - GLFW.GLFW_KEY_RIGHT_SUPER, - GLFW.GLFW_KEY_MENU - )); - - // Key state tracking for press/release detection - private Set previouslyPressedKeys = new HashSet<>(); - private int previousModifiers = 0; - - // Mouse button state tracking (bits: 0=left, 1=right, 2=middle) - private byte previousMouseButtons = 0; - - // Virtual mouse position (absolute coordinates from accumulated deltas) - private double virtualMouseX = 0.0; - private double virtualMouseY = 0.0; - private boolean mouseInitialized = false; - - /** - * Injects a RawInput into Minecraft's input handlers. - * - * @param input The raw input containing key codes, mouse data, and text - */ - public void inject(RawInput input) { - Minecraft mc = Minecraft.getInstance(); - if (mc == null || mc.getWindow() == null) { - LOGGER.warn("Cannot inject input - Minecraft not initialized"); - return; - } - - long window = mc.getWindow().getWindow(); - - // 1. Handle key state changes via KeyboardHandler - handleKeyboardInput(mc, window, input.keyCodes()); - - // 2. Handle mouse movement via MouseHandler.onMove - handleMouseMovement(mc, window, input.mouseDx(), input.mouseDy()); - - // 3. Handle mouse buttons via MouseHandler.onPress - handleMouseButtons(mc, window, input.mouseButtons()); - - // 4. Handle scroll wheel via MouseHandler.onScroll - handleScrollWheel(mc, window, input.scrollDelta()); - - // 5. Handle text input (for chat/signs) - handleTextInput(mc, input.text()); - } - - /** - * Handles keyboard input by detecting press/release transitions and - * calling KeyboardHandler.keyPress() for each event. - */ - private void handleKeyboardInput(Minecraft mc, long window, int[] keyCodes) { - Set currentKeys = Arrays.stream(keyCodes) - .boxed() - .collect(Collectors.toSet()); - - // Find modifier keys and non-modifier keys - Set modifierKeys = findModifierKeys(currentKeys); - Set nonModifierKeys = currentKeys.stream() - .filter(key -> !modifierKeys.contains(key)) - .collect(Collectors.toSet()); - int modifiers = computeModifiers(modifierKeys); - - // Release keys that were pressed but are no longer - for (int key : previouslyPressedKeys) { - if (!nonModifierKeys.contains(key)) { - fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE, previousModifiers); - } - } - - // Press keys that are newly pressed - for (int key : nonModifierKeys) { - if (!previouslyPressedKeys.contains(key)) { - fireKeyEvent(mc, window, key, GLFW.GLFW_PRESS, modifiers); - } - } - - previouslyPressedKeys = nonModifierKeys; - previousModifiers = modifiers; + private static final Logger LOGGER = LogUtils.getLogger(); + + // Modifier keys + private static final Set MODIFIER_KEYS = + new HashSet<>( + Arrays.asList( + GLFW.GLFW_KEY_LEFT_SHIFT, + GLFW.GLFW_KEY_RIGHT_SHIFT, + GLFW.GLFW_KEY_LEFT_CONTROL, + GLFW.GLFW_KEY_RIGHT_CONTROL, + GLFW.GLFW_KEY_LEFT_ALT, + GLFW.GLFW_KEY_RIGHT_ALT, + GLFW.GLFW_KEY_LEFT_SUPER, + GLFW.GLFW_KEY_RIGHT_SUPER, + GLFW.GLFW_KEY_MENU)); + + // Key state tracking for press/release detection + private Set previouslyPressedKeys = new HashSet<>(); + private int previousModifiers = 0; + + // Mouse button state tracking (bits: 0=left, 1=right, 2=middle) + private byte previousMouseButtons = 0; + + // Virtual mouse position (absolute coordinates from accumulated deltas) + private double virtualMouseX = 0.0; + private double virtualMouseY = 0.0; + private boolean mouseInitialized = false; + + /** + * Injects a RawInput into Minecraft's input handlers. + * + * @param input The raw input containing key codes, mouse data, and text + */ + public void inject(RawInput input) { + Minecraft mc = Minecraft.getInstance(); + if (mc == null || mc.getWindow() == null) { + LOGGER.warn("Cannot inject input - Minecraft not initialized"); + return; } - - /** - * Fires a key event through Minecraft's KeyboardHandler. - */ - private void fireKeyEvent(Minecraft mc, long window, int keyCode, int action, int modifiers) { - int scanCode = GLFW.glfwGetKeyScancode(keyCode); - - LOGGER.debug("Firing key event: keyCode={}, scanCode={}, action={}, mods={}", - keyCode, scanCode, action == GLFW.GLFW_PRESS ? "PRESS" : "RELEASE", modifiers); - - // Call the same handler that GLFW callbacks use - mc.keyboardHandler.keyPress(window, keyCode, scanCode, action, modifiers); + + long window = mc.getWindow().getWindow(); + + // 1. Handle key state changes via KeyboardHandler + handleKeyboardInput(mc, window, input.keyCodes()); + + // 2. Handle mouse movement via MouseHandler.onMove + handleMouseMovement(mc, window, input.mouseDx(), input.mouseDy()); + + // 3. Handle mouse buttons via MouseHandler.onPress + handleMouseButtons(mc, window, input.mouseButtons()); + + // 4. Handle scroll wheel via MouseHandler.onScroll + handleScrollWheel(mc, window, input.scrollDelta()); + + // 5. Handle text input (for chat/signs) + handleTextInput(mc, input.text()); + } + + /** + * Handles keyboard input by detecting press/release transitions and calling + * KeyboardHandler.keyPress() for each event. + */ + private void handleKeyboardInput(Minecraft mc, long window, int[] keyCodes) { + Set currentKeys = Arrays.stream(keyCodes).boxed().collect(Collectors.toSet()); + + // Find modifier keys and non-modifier keys + Set modifierKeys = findModifierKeys(currentKeys); + Set nonModifierKeys = + currentKeys.stream().filter(key -> !modifierKeys.contains(key)).collect(Collectors.toSet()); + int modifiers = computeModifiers(modifierKeys); + + // Release keys that were pressed but are no longer + for (int key : previouslyPressedKeys) { + if (!nonModifierKeys.contains(key)) { + fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE, previousModifiers); + } } - /* - * Finds all modifier keys in a set of keys. - * - * @param keys The set of keys to search - * @return The set of modifier keys, or an empty set if every key is a modifier - */ - private Set findModifierKeys(Set keys) { - Set modifiers = keys.stream() - .filter(MODIFIER_KEYS::contains) - .collect(Collectors.toSet()); - // If every key is a modifier, treat as "not used as modifier" so return empty set - if (!modifiers.isEmpty() && modifiers.size() == keys.size()) { - return Collections.emptySet(); - } - return modifiers; + // Press keys that are newly pressed + for (int key : nonModifierKeys) { + if (!previouslyPressedKeys.contains(key)) { + fireKeyEvent(mc, window, key, GLFW.GLFW_PRESS, modifiers); + } } - - /** - * Computes current modifier key state based on pressed keys. - */ - private int computeModifiers(Set modifierKeys) { - int mods = 0; - if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_SHIFT) || - modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_SHIFT)) { - mods |= GLFW.GLFW_MOD_SHIFT; - } - if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_CONTROL) || - modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_CONTROL)) { - mods |= GLFW.GLFW_MOD_CONTROL; - } - if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_ALT) || - modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_ALT)) { - mods |= GLFW.GLFW_MOD_ALT; - } - return mods; + + previouslyPressedKeys = nonModifierKeys; + previousModifiers = modifiers; + } + + /** Fires a key event through Minecraft's KeyboardHandler. */ + private void fireKeyEvent(Minecraft mc, long window, int keyCode, int action, int modifiers) { + int scanCode = GLFW.glfwGetKeyScancode(keyCode); + + LOGGER.debug( + "Firing key event: keyCode={}, scanCode={}, action={}, mods={}", + keyCode, + scanCode, + action == GLFW.GLFW_PRESS ? "PRESS" : "RELEASE", + modifiers); + + // Call the same handler that GLFW callbacks use + mc.keyboardHandler.keyPress(window, keyCode, scanCode, action, modifiers); + } + + /* + * Finds all modifier keys in a set of keys. + * + * @param keys The set of keys to search + * @return The set of modifier keys, or an empty set if every key is a modifier + */ + private Set findModifierKeys(Set keys) { + Set modifiers = + keys.stream().filter(MODIFIER_KEYS::contains).collect(Collectors.toSet()); + // If every key is a modifier, treat as "not used as modifier" so return empty set + if (!modifiers.isEmpty() && modifiers.size() == keys.size()) { + return Collections.emptySet(); } - - /** - * Handles mouse movement by directly rotating the player. - * - * MouseHandler.onMove() only works when the mouse is "grabbed" (captured for gameplay), - * so we bypass it and call player.turn() directly, which is what Minecraft ultimately does. - * - * The delta values are in "pixel" units and get scaled by mouse sensitivity. - */ - private void handleMouseMovement(Minecraft mc, long window, float deltaX, float deltaY) { - if (deltaX == 0 && deltaY == 0) { - return; - } - - // Only rotate the player when in-game (not in menus) - if (mc.player == null || mc.screen != null) { - // If in a menu, we could use onMove for menu interaction - if (mc.screen != null) { - if (!mouseInitialized) { - virtualMouseX = mc.getWindow().getWidth() / 2.0; - virtualMouseY = mc.getWindow().getHeight() / 2.0; - mouseInitialized = true; - } - virtualMouseX += deltaX; - virtualMouseY += deltaY; - mc.mouseHandler.onMove(window, virtualMouseX, virtualMouseY); - } - return; - } - - // Get mouse sensitivity from options (default 0.5, range 0-1) - double sensitivity = mc.options.sensitivity().get() * 0.6 + 0.2; - double sensitivityCubed = sensitivity * sensitivity * sensitivity * 8.0; - - // Convert pixel deltas to rotation deltas (matching Minecraft's turnPlayer logic) - // deltaX affects yaw (horizontal), deltaY affects pitch (vertical) - double yawDelta = deltaX * sensitivityCubed; - double pitchDelta = deltaY * sensitivityCubed; - - LOGGER.debug("Mouse move: delta=({}, {}), yaw={}, pitch={}", - deltaX, deltaY, yawDelta, pitchDelta); - - // Directly rotate the player - // turn(yRot, xRot) where yRot is yaw change and xRot is pitch change - mc.player.turn(yawDelta, pitchDelta); + return modifiers; + } + + /** Computes current modifier key state based on pressed keys. */ + private int computeModifiers(Set modifierKeys) { + int mods = 0; + if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_SHIFT) + || modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_SHIFT)) { + mods |= GLFW.GLFW_MOD_SHIFT; } - - /** - * Handles mouse button state changes. - * - * For continuous actions like mining, we fire press events every tick while held, - * and only fire release events on actual release transitions. - */ - private void handleMouseButtons(Minecraft mc, long window, byte currentButtons) { - int modifiers = previousModifiers; - - // Check each button (0=left, 1=right, 2=middle) - for (int button = 0; button < 3; button++) { - boolean wasDown = (previousMouseButtons & (1 << button)) != 0; - boolean isDown = (currentButtons & (1 << button)) != 0; - - if (isDown) { - // Fire press event every tick while held (for continuous actions) - LOGGER.debug("Mouse button: button={}, action=PRESS (continuous)", button); - mc.mouseHandler.onPress(window, button, GLFW.GLFW_PRESS, modifiers); - } else if (wasDown) { - // Only fire release when transitioning from down to up - LOGGER.debug("Mouse button: button={}, action=RELEASE", button); - mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, modifiers); - } - } - - previousMouseButtons = currentButtons; + if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_CONTROL) + || modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_CONTROL)) { + mods |= GLFW.GLFW_MOD_CONTROL; } - - /** - * Handles scroll wheel input by calling MouseHandler.onScroll(). - */ - private void handleScrollWheel(Minecraft mc, long window, float scrollDelta) { - if (scrollDelta == 0) { - return; - } - - LOGGER.debug("Scroll: delta={}", scrollDelta); - - // Call the same handler that GLFW scroll callbacks use - // xOffset is typically 0 for vertical scrolling, yOffset is the scroll amount - mc.mouseHandler.onScroll(window, 0.0, (double) scrollDelta); + if (modifierKeys.contains(GLFW.GLFW_KEY_LEFT_ALT) + || modifierKeys.contains(GLFW.GLFW_KEY_RIGHT_ALT)) { + mods |= GLFW.GLFW_MOD_ALT; } - - /** - * Handles text input for chat, signs, and other text fields. - * Only processes when a screen is open. - */ - private void handleTextInput(Minecraft mc, String text) { - if (text == null || text.isEmpty()) { - return; - } - - if (mc.screen == null) { - LOGGER.debug("Ignoring text input - no screen open: '{}'", text); - return; - } - - LOGGER.debug("Text input: '{}'", text); - - for (char c : text.toCharArray()) { - mc.screen.charTyped(c, 0); - } + return mods; + } + + /** + * Handles mouse movement by directly rotating the player. + * + *

MouseHandler.onMove() only works when the mouse is "grabbed" (captured for gameplay), so we + * bypass it and call player.turn() directly, which is what Minecraft ultimately does. + * + *

The delta values are in "pixel" units and get scaled by mouse sensitivity. + */ + private void handleMouseMovement(Minecraft mc, long window, float deltaX, float deltaY) { + if (deltaX == 0 && deltaY == 0) { + return; } - - /** - * Resets all input state. Call this when disconnecting or cleaning up. - */ - public void reset() { - Minecraft mc = Minecraft.getInstance(); - if (mc != null && mc.getWindow() != null) { - long window = mc.getWindow().getWindow(); - - // Release all pressed keys - for (int key : previouslyPressedKeys) { - fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE, previousModifiers); - } - - // Release all pressed mouse buttons - for (int button = 0; button < 3; button++) { - if ((previousMouseButtons & (1 << button)) != 0) { - mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, previousModifiers); - } - } + + // Only rotate the player when in-game (not in menus) + if (mc.player == null || mc.screen != null) { + // If in a menu, we could use onMove for menu interaction + if (mc.screen != null) { + if (!mouseInitialized) { + virtualMouseX = mc.getWindow().getWidth() / 2.0; + virtualMouseY = mc.getWindow().getHeight() / 2.0; + mouseInitialized = true; } - - previouslyPressedKeys.clear(); - previousModifiers = 0; - previousMouseButtons = 0; - mouseInitialized = false; - - LOGGER.info("InputInjector reset"); + virtualMouseX += deltaX; + virtualMouseY += deltaY; + mc.mouseHandler.onMove(window, virtualMouseX, virtualMouseY); + } + return; } - - /** - * Gets the current virtual mouse X position. - */ - public double getVirtualMouseX() { - return virtualMouseX; + + // Get mouse sensitivity from options (default 0.5, range 0-1) + double sensitivity = mc.options.sensitivity().get() * 0.6 + 0.2; + double sensitivityCubed = sensitivity * sensitivity * sensitivity * 8.0; + + // Convert pixel deltas to rotation deltas (matching Minecraft's turnPlayer logic) + // deltaX affects yaw (horizontal), deltaY affects pitch (vertical) + double yawDelta = deltaX * sensitivityCubed; + double pitchDelta = deltaY * sensitivityCubed; + + LOGGER.debug( + "Mouse move: delta=({}, {}), yaw={}, pitch={}", deltaX, deltaY, yawDelta, pitchDelta); + + // Directly rotate the player + // turn(yRot, xRot) where yRot is yaw change and xRot is pitch change + mc.player.turn(yawDelta, pitchDelta); + } + + /** + * Handles mouse button state changes. + * + *

For continuous actions like mining, we fire press events every tick while held, and only + * fire release events on actual release transitions. + */ + private void handleMouseButtons(Minecraft mc, long window, byte currentButtons) { + int modifiers = previousModifiers; + + // Check each button (0=left, 1=right, 2=middle) + for (int button = 0; button < 3; button++) { + boolean wasDown = (previousMouseButtons & (1 << button)) != 0; + boolean isDown = (currentButtons & (1 << button)) != 0; + + if (isDown) { + // Fire press event every tick while held (for continuous actions) + LOGGER.debug("Mouse button: button={}, action=PRESS (continuous)", button); + mc.mouseHandler.onPress(window, button, GLFW.GLFW_PRESS, modifiers); + } else if (wasDown) { + // Only fire release when transitioning from down to up + LOGGER.debug("Mouse button: button={}, action=RELEASE", button); + mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, modifiers); + } } - - /** - * Gets the current virtual mouse Y position. - */ - public double getVirtualMouseY() { - return virtualMouseY; + + previousMouseButtons = currentButtons; + } + + /** Handles scroll wheel input by calling MouseHandler.onScroll(). */ + private void handleScrollWheel(Minecraft mc, long window, float scrollDelta) { + if (scrollDelta == 0) { + return; } - - /** - * Gets the set of currently pressed key codes. - */ - public Set getPressedKeys() { - return new HashSet<>(previouslyPressedKeys); + + LOGGER.debug("Scroll: delta={}", scrollDelta); + + // Call the same handler that GLFW scroll callbacks use + // xOffset is typically 0 for vertical scrolling, yOffset is the scroll amount + mc.mouseHandler.onScroll(window, 0.0, (double) scrollDelta); + } + + /** + * Handles text input for chat, signs, and other text fields. Only processes when a screen is + * open. + */ + private void handleTextInput(Minecraft mc, String text) { + if (text == null || text.isEmpty()) { + return; } - /** - * Gets the current modifier key state. - */ - public int getModifiers() { - return previousModifiers; + if (mc.screen == null) { + LOGGER.debug("Ignoring text input - no screen open: '{}'", text); + return; } - - /** - * Gets the current mouse button state. - */ - public byte getMouseButtons() { - return previousMouseButtons; + + LOGGER.debug("Text input: '{}'", text); + + for (char c : text.toCharArray()) { + mc.screen.charTyped(c, 0); } - - /** - * Maintains continuous button state by firing press events every tick. - * This must be called every tick to simulate holding a mouse button. - */ - public void maintainButtonState() { - Minecraft mc = Minecraft.getInstance(); - if (mc == null || mc.getWindow() == null || mc.mouseHandler == null) { - return; - } - - // If any buttons are held, fire press events to maintain the state - if (previousMouseButtons != 0) { - long window = mc.getWindow().getWindow(); - int modifiers = previousModifiers; - - for (int button = 0; button < 3; button++) { - if ((previousMouseButtons & (1 << button)) != 0) { - mc.mouseHandler.onPress(window, button, GLFW.GLFW_PRESS, modifiers); - } - } + } + + /** Resets all input state. Call this when disconnecting or cleaning up. */ + public void reset() { + Minecraft mc = Minecraft.getInstance(); + if (mc != null && mc.getWindow() != null) { + long window = mc.getWindow().getWindow(); + + // Release all pressed keys + for (int key : previouslyPressedKeys) { + fireKeyEvent(mc, window, key, GLFW.GLFW_RELEASE, previousModifiers); + } + + // Release all pressed mouse buttons + for (int button = 0; button < 3; button++) { + if ((previousMouseButtons & (1 << button)) != 0) { + mc.mouseHandler.onPress(window, button, GLFW.GLFW_RELEASE, previousModifiers); } - - // Also set KeyMapping states as backup - if (mc.options != null) { - boolean leftDown = (previousMouseButtons & 1) != 0; - boolean rightDown = (previousMouseButtons & 2) != 0; - mc.options.keyAttack.setDown(leftDown); - mc.options.keyUse.setDown(rightDown); + } + } + + previouslyPressedKeys.clear(); + previousModifiers = 0; + previousMouseButtons = 0; + mouseInitialized = false; + + LOGGER.info("InputInjector reset"); + } + + /** Gets the current virtual mouse X position. */ + public double getVirtualMouseX() { + return virtualMouseX; + } + + /** Gets the current virtual mouse Y position. */ + public double getVirtualMouseY() { + return virtualMouseY; + } + + /** Gets the set of currently pressed key codes. */ + public Set getPressedKeys() { + return new HashSet<>(previouslyPressedKeys); + } + + /** Gets the current modifier key state. */ + public int getModifiers() { + return previousModifiers; + } + + /** Gets the current mouse button state. */ + public byte getMouseButtons() { + return previousMouseButtons; + } + + /** + * Maintains continuous button state by firing press events every tick. This must be called every + * tick to simulate holding a mouse button. + */ + public void maintainButtonState() { + Minecraft mc = Minecraft.getInstance(); + if (mc == null || mc.getWindow() == null || mc.mouseHandler == null) { + return; + } + + // If any buttons are held, fire press events to maintain the state + if (previousMouseButtons != 0) { + long window = mc.getWindow().getWindow(); + int modifiers = previousModifiers; + + for (int button = 0; button < 3; button++) { + if ((previousMouseButtons & (1 << button)) != 0) { + mc.mouseHandler.onPress(window, button, GLFW.GLFW_PRESS, modifiers); } + } + } + + // Also set KeyMapping states as backup + if (mc.options != null) { + boolean leftDown = (previousMouseButtons & 1) != 0; + boolean rightDown = (previousMouseButtons & 2) != 0; + mc.options.keyAttack.setDown(leftDown); + mc.options.keyUse.setDown(rightDown); } + } } diff --git a/forge/src/main/java/com/mineagent/NetworkHandler.java b/forge/src/main/java/com/mineagent/NetworkHandler.java index 8914cba..3cca91e 100644 --- a/forge/src/main/java/com/mineagent/NetworkHandler.java +++ b/forge/src/main/java/com/mineagent/NetworkHandler.java @@ -18,386 +18,382 @@ import org.slf4j.Logger; /** - * Handles network communication between the Minecraft mod and Python agent. - * Uses Unix domain sockets for low-latency IPC. + * Handles network communication between the Minecraft mod and Python agent. Uses Unix domain + * sockets for low-latency IPC. */ public class NetworkHandler implements Runnable { - private static final Logger LOGGER = LogUtils.getLogger(); - - // Socket paths for Unix domain sockets - private static final String OBSERVATION_SOCKET_PATH = "/tmp/mineagent_observation.sock"; - private static final String ACTION_SOCKET_PATH = "/tmp/mineagent_action.sock"; - - // Thread pool for handling clients - private static final ExecutorService observationExecutor = Executors.newCachedThreadPool(); - private static final ExecutorService actionExecutor = Executors.newCachedThreadPool(); - - // Server socket channels - private ServerSocketChannel observationSocketChannel; - private ServerSocketChannel actionSocketChannel; - - // Client handling threads - private Thread observationThread; - private Thread actionThread; - - // Running state - private final AtomicBoolean running = new AtomicBoolean(true); - - // Async observation sending - private final AtomicReference latestObservation = new AtomicReference<>(); - private final Semaphore frameAvailable = new Semaphore(0); - - // Internal observation data container - private static class ObservationData { - final byte[] frameBuffer; - final double reward; - final long timestamp; - - ObservationData(byte[] frameBuffer, double reward) { - this.frameBuffer = frameBuffer; - this.reward = reward; - this.timestamp = System.currentTimeMillis(); - } + private static final Logger LOGGER = LogUtils.getLogger(); + + // Socket paths for Unix domain sockets + private static final String OBSERVATION_SOCKET_PATH = "/tmp/mineagent_observation.sock"; + private static final String ACTION_SOCKET_PATH = "/tmp/mineagent_action.sock"; + + // Thread pool for handling clients + private static final ExecutorService observationExecutor = Executors.newCachedThreadPool(); + private static final ExecutorService actionExecutor = Executors.newCachedThreadPool(); + + // Server socket channels + private ServerSocketChannel observationSocketChannel; + private ServerSocketChannel actionSocketChannel; + + // Client handling threads + private Thread observationThread; + private Thread actionThread; + + // Running state + private final AtomicBoolean running = new AtomicBoolean(true); + + // Async observation sending + private final AtomicReference latestObservation = new AtomicReference<>(); + private final Semaphore frameAvailable = new Semaphore(0); + + // Internal observation data container + private static class ObservationData { + final byte[] frameBuffer; + final double reward; + final long timestamp; + + ObservationData(byte[] frameBuffer, double reward) { + this.frameBuffer = frameBuffer; + this.reward = reward; + this.timestamp = System.currentTimeMillis(); } + } - @Override - public void run() { - try { - // Clean up any existing socket files - Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); - Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); + @Override + public void run() { + try { + // Clean up any existing socket files + Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); + Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); - // Create observation socket - observationSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - observationSocketChannel.bind(UnixDomainSocketAddress.of(OBSERVATION_SOCKET_PATH)); - observationSocketChannel.configureBlocking(true); + // Create observation socket + observationSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + observationSocketChannel.bind(UnixDomainSocketAddress.of(OBSERVATION_SOCKET_PATH)); + observationSocketChannel.configureBlocking(true); - // Create action socket - actionSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - actionSocketChannel.bind(UnixDomainSocketAddress.of(ACTION_SOCKET_PATH)); - actionSocketChannel.configureBlocking(true); + // Create action socket + actionSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + actionSocketChannel.bind(UnixDomainSocketAddress.of(ACTION_SOCKET_PATH)); + actionSocketChannel.configureBlocking(true); - LOGGER.info("Socket files created: {} and {}", OBSERVATION_SOCKET_PATH, ACTION_SOCKET_PATH); + LOGGER.info("Socket files created: {} and {}", OBSERVATION_SOCKET_PATH, ACTION_SOCKET_PATH); - // Verify socket files were created - if (!Files.exists(Path.of(OBSERVATION_SOCKET_PATH))) { - throw new IOException("Failed to create observation socket file: " + OBSERVATION_SOCKET_PATH); - } - if (!Files.exists(Path.of(ACTION_SOCKET_PATH))) { - throw new IOException("Failed to create action socket file: " + ACTION_SOCKET_PATH); - } + // Verify socket files were created + if (!Files.exists(Path.of(OBSERVATION_SOCKET_PATH))) { + throw new IOException( + "Failed to create observation socket file: " + OBSERVATION_SOCKET_PATH); + } + if (!Files.exists(Path.of(ACTION_SOCKET_PATH))) { + throw new IOException("Failed to create action socket file: " + ACTION_SOCKET_PATH); + } - LOGGER.info("Socket files verified - Observation: {}, Action: {}", - Files.exists(Path.of(OBSERVATION_SOCKET_PATH)), - Files.exists(Path.of(ACTION_SOCKET_PATH))); + LOGGER.info( + "Socket files verified - Observation: {}, Action: {}", + Files.exists(Path.of(OBSERVATION_SOCKET_PATH)), + Files.exists(Path.of(ACTION_SOCKET_PATH))); - // Start client acceptor threads - observationThread = new Thread(this::acceptObservationClients, "ObservationClients"); - actionThread = new Thread(this::acceptActionClients, "ActionClients"); + // Start client acceptor threads + observationThread = new Thread(this::acceptObservationClients, "ObservationClients"); + actionThread = new Thread(this::acceptActionClients, "ActionClients"); - observationThread.start(); - actionThread.start(); + observationThread.start(); + actionThread.start(); - // Keep main thread alive while server is running - while (this.running.get() && !Thread.currentThread().isInterrupted()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } + // Keep main thread alive while server is running + while (this.running.get() && !Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + } catch (IOException e) { + LOGGER.error("Error starting network server", e); + } finally { + this.cleanup(); + } + } - } catch (IOException e) { - LOGGER.error("Error starting network server", e); - } finally { - this.cleanup(); + private void acceptObservationClients() { + LOGGER.info("Observation clients acceptor thread started"); + while (this.running.get() && !Thread.currentThread().isInterrupted()) { + try { + SocketChannel clientSocket = observationSocketChannel.accept(); + LOGGER.info("Observation client connected: {}", clientSocket.getRemoteAddress()); + // 1MB send buffer for frames + clientSocket.setOption(StandardSocketOptions.SO_SNDBUF, 1024 * 1024); + handleObservationClient(clientSocket); + } catch (IOException e) { + if (this.running.get()) { + LOGGER.error("Error accepting observation client", e); + } else { + LOGGER.info("Observation socket channel closed, stopping accept loop"); + break; } + } } + LOGGER.info("Observation clients acceptor thread stopped"); + } - private void acceptObservationClients() { - LOGGER.info("Observation clients acceptor thread started"); - while (this.running.get() && !Thread.currentThread().isInterrupted()) { - try { - SocketChannel clientSocket = observationSocketChannel.accept(); - LOGGER.info("Observation client connected: {}", clientSocket.getRemoteAddress()); - // 1MB send buffer for frames - clientSocket.setOption(StandardSocketOptions.SO_SNDBUF, 1024 * 1024); - handleObservationClient(clientSocket); - } catch (IOException e) { - if (this.running.get()) { - LOGGER.error("Error accepting observation client", e); - } else { - LOGGER.info("Observation socket channel closed, stopping accept loop"); - break; - } - } + private void acceptActionClients() { + LOGGER.info("Action clients acceptor thread started"); + while (this.running.get() && !Thread.currentThread().isInterrupted()) { + try { + SocketChannel clientSocket = actionSocketChannel.accept(); + LOGGER.info("Action client connected: {}", clientSocket.getRemoteAddress()); + + // Mark client as connected for input suppression + DataBridge.getInstance().setClientConnected(true); + + handleActionClient(clientSocket); + } catch (IOException e) { + if (this.running.get()) { + LOGGER.error("Error accepting action client", e); + } else { + LOGGER.info("Action socket channel closed, stopping accept loop"); + break; } - LOGGER.info("Observation clients acceptor thread stopped"); + } } + LOGGER.info("Action clients acceptor thread stopped"); + } - private void acceptActionClients() { - LOGGER.info("Action clients acceptor thread started"); - while (this.running.get() && !Thread.currentThread().isInterrupted()) { - try { - SocketChannel clientSocket = actionSocketChannel.accept(); - LOGGER.info("Action client connected: {}", clientSocket.getRemoteAddress()); - - // Mark client as connected for input suppression - DataBridge.getInstance().setClientConnected(true); - - handleActionClient(clientSocket); - } catch (IOException e) { - if (this.running.get()) { - LOGGER.error("Error accepting action client", e); - } else { - LOGGER.info("Action socket channel closed, stopping accept loop"); - break; + /** + * Handles an action client connection, reading variable-size RawInput messages. + * + *

RawInput protocol format: - 1 byte: numKeysPressed (0-255) - N*2 bytes: keyCodes (shorts) - + * 4 bytes: mouseDeltaX (float) - 4 bytes: mouseDeltaY (float) - 1 byte: mouseButtons - 4 bytes: + * scrollDelta (float) - 2 bytes: textLength - M bytes: textBytes (UTF-8) + * + *

Minimum size: 16 bytes (no keys, no text) + */ + private void handleActionClient(SocketChannel clientSocket) { + actionExecutor.submit( + () -> { + try { + clientSocket.configureBlocking(true); + + // Buffer for reading the header (1 byte for key count) + ByteBuffer headerBuffer = ByteBuffer.allocate(1); + + while (this.running.get()) { + // Step 1: Read the key count (1 byte) + headerBuffer.clear(); + if (readExact(clientSocket, headerBuffer) == -1) { + LOGGER.info("Action client disconnected"); + break; + } + headerBuffer.flip(); + int numKeys = headerBuffer.get() & 0xFF; + + // Step 2: Calculate remaining message size + // keyCodes(N*2) + mouseDx(4) + mouseDy(4) + mouseButtons(1) + scrollDelta(4) + + // textLen(2) + int fixedSize = (numKeys * 2) + 4 + 4 + 1 + 4 + 2; + ByteBuffer fixedBuffer = ByteBuffer.allocate(fixedSize); + + if (readExact(clientSocket, fixedBuffer) == -1) { + LOGGER.info("Action client disconnected during fixed read"); + break; + } + fixedBuffer.flip(); + + // Read key codes + int[] keyCodes = new int[numKeys]; + for (int i = 0; i < numKeys; i++) { + keyCodes[i] = fixedBuffer.getShort(); + } + + // Read mouse and scroll data + float mouseDx = fixedBuffer.getFloat(); + float mouseDy = fixedBuffer.getFloat(); + byte mouseButtons = fixedBuffer.get(); + float scrollDelta = fixedBuffer.getFloat(); + + // Read text length + int textLength = fixedBuffer.getShort() & 0xFFFF; + + // Step 3: Read text if present + String text = ""; + if (textLength > 0) { + ByteBuffer textBuffer = ByteBuffer.allocate(textLength); + if (readExact(clientSocket, textBuffer) == -1) { + LOGGER.info("Action client disconnected during text read"); + break; } + textBuffer.flip(); + byte[] textBytes = new byte[textLength]; + textBuffer.get(textBytes); + text = new String(textBytes, java.nio.charset.StandardCharsets.UTF_8); + } + + // Create and process the RawInput + final RawInput rawInput = + new RawInput(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + processRawInput(rawInput); } - } - LOGGER.info("Action clients acceptor thread stopped"); - } + } catch (IOException e) { + if (this.running.get()) { + LOGGER.error("Error handling action client", e); + } + } finally { + // Mark client as disconnected + DataBridge.getInstance().setClientConnected(false); + + // Reset input state on disconnect + DataBridge.getInstance().getInputInjector().reset(); - /** - * Handles an action client connection, reading variable-size RawInput messages. - * - * RawInput protocol format: - * - 1 byte: numKeysPressed (0-255) - * - N*2 bytes: keyCodes (shorts) - * - 4 bytes: mouseDeltaX (float) - * - 4 bytes: mouseDeltaY (float) - * - 1 byte: mouseButtons - * - 4 bytes: scrollDelta (float) - * - 2 bytes: textLength - * - M bytes: textBytes (UTF-8) - * - * Minimum size: 16 bytes (no keys, no text) - */ - private void handleActionClient(SocketChannel clientSocket) { - actionExecutor.submit(() -> { try { - clientSocket.configureBlocking(true); - - // Buffer for reading the header (1 byte for key count) - ByteBuffer headerBuffer = ByteBuffer.allocate(1); - - while (this.running.get()) { - // Step 1: Read the key count (1 byte) - headerBuffer.clear(); - if (readExact(clientSocket, headerBuffer) == -1) { - LOGGER.info("Action client disconnected"); - break; - } - headerBuffer.flip(); - int numKeys = headerBuffer.get() & 0xFF; - - // Step 2: Calculate remaining message size - // keyCodes(N*2) + mouseDx(4) + mouseDy(4) + mouseButtons(1) + scrollDelta(4) + textLen(2) - int fixedSize = (numKeys * 2) + 4 + 4 + 1 + 4 + 2; - ByteBuffer fixedBuffer = ByteBuffer.allocate(fixedSize); - - if (readExact(clientSocket, fixedBuffer) == -1) { - LOGGER.info("Action client disconnected during fixed read"); - break; - } - fixedBuffer.flip(); - - // Read key codes - int[] keyCodes = new int[numKeys]; - for (int i = 0; i < numKeys; i++) { - keyCodes[i] = fixedBuffer.getShort(); - } - - // Read mouse and scroll data - float mouseDx = fixedBuffer.getFloat(); - float mouseDy = fixedBuffer.getFloat(); - byte mouseButtons = fixedBuffer.get(); - float scrollDelta = fixedBuffer.getFloat(); - - // Read text length - int textLength = fixedBuffer.getShort() & 0xFFFF; - - // Step 3: Read text if present - String text = ""; - if (textLength > 0) { - ByteBuffer textBuffer = ByteBuffer.allocate(textLength); - if (readExact(clientSocket, textBuffer) == -1) { - LOGGER.info("Action client disconnected during text read"); - break; - } - textBuffer.flip(); - byte[] textBytes = new byte[textLength]; - textBuffer.get(textBytes); - text = new String(textBytes, java.nio.charset.StandardCharsets.UTF_8); - } - - // Create and process the RawInput - final RawInput rawInput = new RawInput(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - processRawInput(rawInput); - } + clientSocket.close(); } catch (IOException e) { - if (this.running.get()) { - LOGGER.error("Error handling action client", e); - } - } finally { - // Mark client as disconnected - DataBridge.getInstance().setClientConnected(false); - - // Reset input state on disconnect - DataBridge.getInstance().getInputInjector().reset(); - - try { - clientSocket.close(); - } catch (IOException e) { - LOGGER.error("Error closing action client socket", e); - } + LOGGER.error("Error closing action client socket", e); } + } }); - } + } - /** - * Reads exactly the buffer's remaining capacity from the socket. - * Returns -1 if the client disconnects, otherwise returns bytes read. - */ - private int readExact(SocketChannel channel, ByteBuffer buffer) throws IOException { - int totalRead = 0; - while (buffer.hasRemaining()) { - int bytesRead = channel.read(buffer); - if (bytesRead == -1) { - return -1; - } - totalRead += bytesRead; - } - return totalRead; + /** + * Reads exactly the buffer's remaining capacity from the socket. Returns -1 if the client + * disconnects, otherwise returns bytes read. + */ + private int readExact(SocketChannel channel, ByteBuffer buffer) throws IOException { + int totalRead = 0; + while (buffer.hasRemaining()) { + int bytesRead = channel.read(buffer); + if (bytesRead == -1) { + return -1; + } + totalRead += bytesRead; } + return totalRead; + } - /** - * Processes a received RawInput by passing it to the DataBridge. - */ - private void processRawInput(RawInput rawInput) { - DataBridge.getInstance().setLatestRawInput(rawInput); - LOGGER.debug("RawInput received: {} keys, mouse=({}, {}), buttons={}, scroll={}, text='{}'", - rawInput.keyCodes().length, - rawInput.mouseDx(), rawInput.mouseDy(), - rawInput.mouseButtons(), - rawInput.scrollDelta(), - rawInput.text()); - } + /** Processes a received RawInput by passing it to the DataBridge. */ + private void processRawInput(RawInput rawInput) { + DataBridge.getInstance().setLatestRawInput(rawInput); + LOGGER.debug( + "RawInput received: {} keys, mouse=({}, {}), buttons={}, scroll={}, text='{}'", + rawInput.keyCodes().length, + rawInput.mouseDx(), + rawInput.mouseDy(), + rawInput.mouseButtons(), + rawInput.scrollDelta(), + rawInput.text()); + } - private void handleObservationClient(SocketChannel clientSocket) { - observationExecutor.submit(() -> { - try { - while (this.running.get()) { - try { - frameAvailable.acquire(); - ObservationData observation = latestObservation.get(); - if (observation != null) { - sendObservationImmediate(observation, clientSocket); - } - } catch (InterruptedException e) { - LOGGER.info("Observation thread interrupted"); - Thread.currentThread().interrupt(); - break; - } - } - } finally { - try { - clientSocket.close(); - } catch (IOException e) { - LOGGER.error("Error closing observation client socket", e); + private void handleObservationClient(SocketChannel clientSocket) { + observationExecutor.submit( + () -> { + try { + while (this.running.get()) { + try { + frameAvailable.acquire(); + ObservationData observation = latestObservation.get(); + if (observation != null) { + sendObservationImmediate(observation, clientSocket); } + } catch (InterruptedException e) { + LOGGER.info("Observation thread interrupted"); + Thread.currentThread().interrupt(); + break; + } } + } finally { + try { + clientSocket.close(); + } catch (IOException e) { + LOGGER.error("Error closing observation client socket", e); + } + } }); - } + } - /** - * Sets the latest observation data to be sent to connected clients. - */ - public void setLatest(byte[] frameBuffer, double reward) { - ObservationData observation = new ObservationData(frameBuffer, reward); - latestObservation.set(observation); - frameAvailable.drainPermits(); - frameAvailable.release(); - } + /** Sets the latest observation data to be sent to connected clients. */ + public void setLatest(byte[] frameBuffer, double reward) { + ObservationData observation = new ObservationData(frameBuffer, reward); + latestObservation.set(observation); + frameAvailable.drainPermits(); + frameAvailable.release(); + } - private void sendObservationImmediate(ObservationData observation, SocketChannel clientSocket) { - try { - // Format: reward(8) + frameLength(4) + frame(N) - int totalSize = 8 + 4 + observation.frameBuffer.length; - ByteBuffer buffer = ByteBuffer.allocate(totalSize); - buffer.putDouble(observation.reward); - buffer.putInt(observation.frameBuffer.length); - buffer.put(observation.frameBuffer); - buffer.flip(); - - while (buffer.hasRemaining()) { - clientSocket.write(buffer); - } - LOGGER.debug("Observation sent: {} bytes", totalSize); - } catch (IOException e) { - LOGGER.error("Error sending observation", e); - } + private void sendObservationImmediate(ObservationData observation, SocketChannel clientSocket) { + try { + // Format: reward(8) + frameLength(4) + frame(N) + int totalSize = 8 + 4 + observation.frameBuffer.length; + ByteBuffer buffer = ByteBuffer.allocate(totalSize); + buffer.putDouble(observation.reward); + buffer.putInt(observation.frameBuffer.length); + buffer.put(observation.frameBuffer); + buffer.flip(); + + while (buffer.hasRemaining()) { + clientSocket.write(buffer); + } + LOGGER.debug("Observation sent: {} bytes", totalSize); + } catch (IOException e) { + LOGGER.error("Error sending observation", e); } + } - private void cleanup() { - LOGGER.info("Shutting down NetworkHandler..."); - this.running.set(false); - - // Reset input state on cleanup - DataBridge.getInstance().getInputInjector().reset(); - DataBridge.getInstance().setClientConnected(false); - - // Wait for threads to finish - if (this.observationThread != null) { - try { - this.observationThread.interrupt(); - this.observationThread.join(5000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } + private void cleanup() { + LOGGER.info("Shutting down NetworkHandler..."); + this.running.set(false); - if (this.actionThread != null) { - try { - this.actionThread.interrupt(); - this.actionThread.join(5000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } + // Reset input state on cleanup + DataBridge.getInstance().getInputInjector().reset(); + DataBridge.getInstance().setClientConnected(false); - // Shutdown executors - observationExecutor.shutdown(); - actionExecutor.shutdown(); + // Wait for threads to finish + if (this.observationThread != null) { + try { + this.observationThread.interrupt(); + this.observationThread.join(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } - // Close socket channels - try { - if (this.observationSocketChannel != null) { - this.observationSocketChannel.close(); - } - if (this.actionSocketChannel != null) { - this.actionSocketChannel.close(); - } - } catch (IOException e) { - LOGGER.error("Error closing socket channels: {}", e.getMessage()); - } + if (this.actionThread != null) { + try { + this.actionThread.interrupt(); + this.actionThread.join(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } - // Delete socket files - try { - Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); - Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); - } catch (IOException e) { - LOGGER.error("Error deleting socket files: {}", e.getMessage()); - } + // Shutdown executors + observationExecutor.shutdown(); + actionExecutor.shutdown(); - LOGGER.info("NetworkHandler shutdown complete"); + // Close socket channels + try { + if (this.observationSocketChannel != null) { + this.observationSocketChannel.close(); + } + if (this.actionSocketChannel != null) { + this.actionSocketChannel.close(); + } + } catch (IOException e) { + LOGGER.error("Error closing socket channels: {}", e.getMessage()); } - - /** - * Stops the network handler gracefully. - */ - public void stop() { - this.running.set(false); + + // Delete socket files + try { + Files.deleteIfExists(Path.of(OBSERVATION_SOCKET_PATH)); + Files.deleteIfExists(Path.of(ACTION_SOCKET_PATH)); + } catch (IOException e) { + LOGGER.error("Error deleting socket files: {}", e.getMessage()); } + + LOGGER.info("NetworkHandler shutdown complete"); + } + + /** Stops the network handler gracefully. */ + public void stop() { + this.running.set(false); + } } diff --git a/forge/src/main/java/com/mineagent/Observation.java b/forge/src/main/java/com/mineagent/Observation.java index 56629cd..8a804d5 100644 --- a/forge/src/main/java/com/mineagent/Observation.java +++ b/forge/src/main/java/com/mineagent/Observation.java @@ -1,10 +1,7 @@ package com.mineagent; -public record Observation( - double reward, - byte[] frame -) { - public byte[] serialize() { - return frame; - } +public record Observation(double reward, byte[] frame) { + public byte[] serialize() { + return frame; + } } diff --git a/forge/src/main/java/com/mineagent/RawInput.java b/forge/src/main/java/com/mineagent/RawInput.java index dc6ca78..0944b15 100644 --- a/forge/src/main/java/com/mineagent/RawInput.java +++ b/forge/src/main/java/com/mineagent/RawInput.java @@ -10,38 +10,31 @@ public record RawInput( float mouseDy, byte mouseButtons, float scrollDelta, - String text -) { - public static RawInput fromBytes(byte[] bytes) { - ByteBuffer buffer = ByteBuffer.wrap(bytes); - buffer.order(ByteOrder.BIG_ENDIAN); // Match Python protocol (struct.pack with '>') - - // Keys - int numKeysPressed = buffer.get() & 0xFF; - int[] keyCodes = new int[numKeysPressed]; - for (int i = 0; i < numKeysPressed; i++) { - keyCodes[i] = buffer.getShort(); - } + String text) { + public static RawInput fromBytes(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.order(ByteOrder.BIG_ENDIAN); // Match Python protocol (struct.pack with '>') - // Mouse - float mouseDx = buffer.getFloat(); - float mouseDy = buffer.getFloat(); - byte mouseButtons = buffer.get(); - float scrollDelta = buffer.getFloat(); + // Keys + int numKeysPressed = buffer.get() & 0xFF; + int[] keyCodes = new int[numKeysPressed]; + for (int i = 0; i < numKeysPressed; i++) { + keyCodes[i] = buffer.getShort(); + } - // Text (for typing) - int textLength = buffer.getShort() & 0xFFFF; - byte[] textBytes = new byte[textLength]; - buffer.get(textBytes); - String text = new String(textBytes, StandardCharsets.UTF_8); + // Mouse + float mouseDx = buffer.getFloat(); + float mouseDy = buffer.getFloat(); + byte mouseButtons = buffer.get(); + float scrollDelta = buffer.getFloat(); - return new RawInput( - keyCodes, - mouseDx, - mouseDy, - mouseButtons, - scrollDelta, - text - ); - } -}; + // Text (for typing) + int textLength = buffer.getShort() & 0xFFFF; + byte[] textBytes = new byte[textLength]; + buffer.get(textBytes); + String text = new String(textBytes, StandardCharsets.UTF_8); + + return new RawInput(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + } +} +; diff --git a/forge/src/test/java/com/mineagent/InputInjectorTest.java b/forge/src/test/java/com/mineagent/InputInjectorTest.java index c1b0532..ba48dca 100644 --- a/forge/src/test/java/com/mineagent/InputInjectorTest.java +++ b/forge/src/test/java/com/mineagent/InputInjectorTest.java @@ -5,13 +5,11 @@ import static org.mockito.Mockito.*; import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.lwjgl.glfw.GLFW; import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; -import org.mockito.quality.Strictness; class InputInjectorTest { @@ -39,11 +37,7 @@ void inject_withNewKeyPressed_firesKeyPressEvent() { verify(fixture.getKeyboardHandler(), times(1)) .keyPress( - eq(WINDOW_HANDLE), - keyCodeCaptor.capture(), - anyInt(), - actionCaptor.capture(), - eq(0)); + eq(WINDOW_HANDLE), keyCodeCaptor.capture(), anyInt(), actionCaptor.capture(), eq(0)); assertEquals(GLFW.GLFW_KEY_W, keyCodeCaptor.getValue()); assertEquals(GLFW.GLFW_PRESS, actionCaptor.getValue()); @@ -56,7 +50,8 @@ void inject_withKeyReleased_firesKeyReleaseEvent() { mockedMinecraft.when(Minecraft::getInstance).thenReturn(fixture.getMinecraft()); // First inject with W key (press) - RawInput pressInput = new RawInput(new int[] {GLFW.GLFW_KEY_W}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); + RawInput pressInput = + new RawInput(new int[] {GLFW.GLFW_KEY_W}, 0.0f, 0.0f, (byte) 0, 0.0f, ""); injector.inject(pressInput); // Then inject with no keys (release) @@ -130,11 +125,7 @@ void inject_withOnlyModifierKey_firesAsRegularKey() { // Shift should fire as a regular key when pressed alone verify(fixture.getKeyboardHandler(), times(1)) .keyPress( - eq(WINDOW_HANDLE), - keyCodeCaptor.capture(), - anyInt(), - eq(GLFW.GLFW_PRESS), - eq(0)); + eq(WINDOW_HANDLE), keyCodeCaptor.capture(), anyInt(), eq(GLFW.GLFW_PRESS), eq(0)); assertEquals(GLFW.GLFW_KEY_LEFT_SHIFT, keyCodeCaptor.getValue()); } @@ -195,7 +186,7 @@ void inject_withMouseMovement_rotatesPlayer() throws Exception { fixture.setMinecraftField("player", player); // Set screen to null using fixture helper fixture.setMinecraftField("screen", null); - + // Mock sensitivity() to return an OptionInstance that returns 0.5 net.minecraft.client.OptionInstance sensitivityOption = mock(net.minecraft.client.OptionInstance.class, withSettings().strictness(Strictness.LENIENT)); diff --git a/forge/src/test/java/com/mineagent/MinecraftTestFixture.java b/forge/src/test/java/com/mineagent/MinecraftTestFixture.java index ab8ceb8..73d1f81 100644 --- a/forge/src/test/java/com/mineagent/MinecraftTestFixture.java +++ b/forge/src/test/java/com/mineagent/MinecraftTestFixture.java @@ -1,21 +1,23 @@ package com.mineagent; +import static org.mockito.Mockito.*; + import com.mojang.blaze3d.platform.Window; +import java.lang.reflect.Field; +import net.minecraft.client.KeyMapping; import net.minecraft.client.KeyboardHandler; import net.minecraft.client.Minecraft; import net.minecraft.client.MouseHandler; -import net.minecraft.client.KeyMapping; import net.minecraft.client.Options; import net.minecraft.client.gui.screens.Screen; -import java.lang.reflect.Field; -import static org.mockito.Mockito.*; import org.mockito.quality.Strictness; /** - * Test fixture for setting up Minecraft mocks with all necessary fields configured. - * This handles the reflection needed to set public fields on Minecraft and Options classes. - * - * Usage: + * Test fixture for setting up Minecraft mocks with all necessary fields configured. This handles + * the reflection needed to set public fields on Minecraft and Options classes. + * + *

Usage: + * *

  * MinecraftTestFixture fixture = new MinecraftTestFixture();
  * Minecraft mc = fixture.getMinecraft();
@@ -31,63 +33,57 @@ public class MinecraftTestFixture {
   private final KeyMapping keyAttack;
   private final KeyMapping keyUse;
   private final Screen screen;
-  
+
   private static final long DEFAULT_WINDOW_HANDLE = 12345L;
 
-  /**
-   * Creates a new test fixture with all mocks configured.
-   * The window handle defaults to 12345L.
-   */
+  /** Creates a new test fixture with all mocks configured. The window handle defaults to 12345L. */
   public MinecraftTestFixture() {
     this(DEFAULT_WINDOW_HANDLE);
   }
 
   /**
    * Creates a new test fixture with a custom window handle.
-   * 
+   *
    * @param windowHandle The GLFW window handle to use
    */
   public MinecraftTestFixture(long windowHandle) {
     // Create all mocks with lenient settings
     this.mc = mock(Minecraft.class, withSettings().strictness(Strictness.LENIENT));
     this.window = mock(Window.class, withSettings().strictness(Strictness.LENIENT));
-    this.keyboardHandler = mock(KeyboardHandler.class, withSettings().strictness(Strictness.LENIENT));
+    this.keyboardHandler =
+        mock(KeyboardHandler.class, withSettings().strictness(Strictness.LENIENT));
     this.mouseHandler = mock(MouseHandler.class, withSettings().strictness(Strictness.LENIENT));
     this.options = mock(Options.class, withSettings().strictness(Strictness.LENIENT));
     this.keyAttack = mock(KeyMapping.class, withSettings().strictness(Strictness.LENIENT));
     this.keyUse = mock(KeyMapping.class, withSettings().strictness(Strictness.LENIENT));
     this.screen = mock(Screen.class, withSettings().strictness(Strictness.LENIENT));
-    
+
     // Setup common mock behavior
     when(window.getWindow()).thenReturn(windowHandle);
     when(mc.getWindow()).thenReturn(window);
-    
+
     // Set fields using reflection
     setupMinecraftFields();
     setupOptionsFields();
   }
 
-  /**
-   * Sets up all Minecraft class fields using reflection.
-   */
+  /** Sets up all Minecraft class fields using reflection. */
   private void setupMinecraftFields() {
     setField(Minecraft.class, mc, "keyboardHandler", keyboardHandler);
     setField(Minecraft.class, mc, "mouseHandler", mouseHandler);
     setField(Minecraft.class, mc, "options", options);
   }
 
-  /**
-   * Sets up all Options class fields using reflection.
-   */
+  /** Sets up all Options class fields using reflection. */
   private void setupOptionsFields() {
     setField(Options.class, options, "keyAttack", keyAttack);
     setField(Options.class, options, "keyUse", keyUse);
   }
 
   /**
-   * Sets a field on an object using reflection.
-   * Silently fails if the field doesn't exist or can't be accessed.
-   * 
+   * Sets a field on an object using reflection. Silently fails if the field doesn't exist or can't
+   * be accessed.
+   *
    * @param clazz The class containing the field
    * @param target The object instance to set the field on
    * @param fieldName The name of the field
@@ -106,9 +102,9 @@ private static void setField(Class clazz, Object target, String fieldName, Ob
   }
 
   /**
-   * Sets a field on the Minecraft instance.
-   * Useful for setting fields like "player" or "screen" in specific tests.
-   * 
+   * Sets a field on the Minecraft instance. Useful for setting fields like "player" or "screen" in
+   * specific tests.
+   *
    * @param fieldName The name of the field
    * @param value The value to set
    */
@@ -118,7 +114,7 @@ public void setMinecraftField(String fieldName, Object value) {
 
   /**
    * Sets a field on the Options instance.
-   * 
+   *
    * @param fieldName The name of the field
    * @param value The value to set
    */
@@ -127,7 +123,7 @@ public void setOptionsField(String fieldName, Object value) {
   }
 
   // Getters for all mocked components
-  
+
   public Minecraft getMinecraft() {
     return mc;
   }
diff --git a/forge/src/test/java/com/mineagent/RawInputTest.java b/forge/src/test/java/com/mineagent/RawInputTest.java
index fc3f0bd..d796c50 100644
--- a/forge/src/test/java/com/mineagent/RawInputTest.java
+++ b/forge/src/test/java/com/mineagent/RawInputTest.java
@@ -9,254 +9,254 @@
 
 class RawInputTest {
 
-    /**
-     * Helper method to create a byte array representing a RawInput.
-     *
-     * 

Binary format: - * - *

    - *
  • 1 byte: number of keys pressed (unsigned) - *
  • N × 2 bytes: key codes as shorts - *
  • 4 bytes: mouseDx as float - *
  • 4 bytes: mouseDy as float - *
  • 1 byte: mouseButtons - *
  • 4 bytes: scrollDelta as float - *
  • 2 bytes: text length (unsigned short) - *
  • M bytes: UTF-8 encoded text - *
- */ - private byte[] createRawInputBytes( - int[] keyCodes, - float mouseDx, - float mouseDy, - byte mouseButtons, - float scrollDelta, - String text) { - byte[] textBytes = text.getBytes(StandardCharsets.UTF_8); - int bufferSize = 1 + (keyCodes.length * 2) + 4 + 4 + 1 + 4 + 2 + textBytes.length; - ByteBuffer buffer = ByteBuffer.allocate(bufferSize); - buffer.order(ByteOrder.BIG_ENDIAN); // Match Python protocol (struct.pack with '>') - - buffer.put((byte) keyCodes.length); - for (int keyCode : keyCodes) { - buffer.putShort((short) keyCode); - } - buffer.putFloat(mouseDx); - buffer.putFloat(mouseDy); - buffer.put(mouseButtons); - buffer.putFloat(scrollDelta); - buffer.putShort((short) textBytes.length); - buffer.put(textBytes); - - return buffer.array(); + /** + * Helper method to create a byte array representing a RawInput. + * + *

Binary format: + * + *

    + *
  • 1 byte: number of keys pressed (unsigned) + *
  • N × 2 bytes: key codes as shorts + *
  • 4 bytes: mouseDx as float + *
  • 4 bytes: mouseDy as float + *
  • 1 byte: mouseButtons + *
  • 4 bytes: scrollDelta as float + *
  • 2 bytes: text length (unsigned short) + *
  • M bytes: UTF-8 encoded text + *
+ */ + private byte[] createRawInputBytes( + int[] keyCodes, + float mouseDx, + float mouseDy, + byte mouseButtons, + float scrollDelta, + String text) { + byte[] textBytes = text.getBytes(StandardCharsets.UTF_8); + int bufferSize = 1 + (keyCodes.length * 2) + 4 + 4 + 1 + 4 + 2 + textBytes.length; + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + buffer.order(ByteOrder.BIG_ENDIAN); // Match Python protocol (struct.pack with '>') + + buffer.put((byte) keyCodes.length); + for (int keyCode : keyCodes) { + buffer.putShort((short) keyCode); } - - @Test - void fromBytes_withTypicalInput_parsesCorrectly() { - int[] keyCodes = {87, 32}; // W key and Space - float mouseDx = 10.5f; - float mouseDy = -5.25f; - byte mouseButtons = 0b00000001; // Left button pressed - float scrollDelta = 1.0f; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertArrayEquals(keyCodes, result.keyCodes()); - assertEquals(mouseDx, result.mouseDx(), 0.0001f); - assertEquals(mouseDy, result.mouseDy(), 0.0001f); - assertEquals(mouseButtons, result.mouseButtons()); - assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); - assertEquals(text, result.text()); - } - - @Test - void fromBytes_withNoKeysPressed_parsesEmptyKeyArray() { - int[] keyCodes = {}; - float mouseDx = 0.0f; - float mouseDy = 0.0f; - byte mouseButtons = 0; - float scrollDelta = 0.0f; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertArrayEquals(keyCodes, result.keyCodes()); - assertEquals(0, result.keyCodes().length); - } - - @Test - void fromBytes_withMultipleKeys_parsesAllKeys() { - int[] keyCodes = {87, 65, 83, 68, 340}; // WASD + Left Shift - float mouseDx = 0.0f; - float mouseDy = 0.0f; - byte mouseButtons = 0; - float scrollDelta = 0.0f; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertArrayEquals(keyCodes, result.keyCodes()); - assertEquals(5, result.keyCodes().length); - } - - @Test - void fromBytes_withNegativeMouseDeltas_parsesCorrectly() { - int[] keyCodes = {}; - float mouseDx = -100.75f; - float mouseDy = -200.5f; - byte mouseButtons = 0; - float scrollDelta = -3.0f; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertEquals(mouseDx, result.mouseDx(), 0.0001f); - assertEquals(mouseDy, result.mouseDy(), 0.0001f); - assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); - } - - @Test - void fromBytes_withAllMouseButtonsPressed_parsesCorrectly() { - int[] keyCodes = {}; - float mouseDx = 0.0f; - float mouseDy = 0.0f; - byte mouseButtons = (byte) 0xFF; // All buttons pressed - float scrollDelta = 0.0f; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertEquals((byte) 0xFF, result.mouseButtons()); - } - - @Test - void fromBytes_withText_parsesTextCorrectly() { - int[] keyCodes = {}; - float mouseDx = 0.0f; - float mouseDy = 0.0f; - byte mouseButtons = 0; - float scrollDelta = 0.0f; - String text = "Hello World!"; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertEquals(text, result.text()); - } - - @Test - void fromBytes_withUnicodeText_parsesUtf8Correctly() { - int[] keyCodes = {}; - float mouseDx = 0.0f; - float mouseDy = 0.0f; - byte mouseButtons = 0; - float scrollDelta = 0.0f; - String text = "日本語テスト 🎮"; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertEquals(text, result.text()); - } - - @Test - void fromBytes_withCompleteInput_parsesAllFieldsCorrectly() { - int[] keyCodes = {69, 256}; // E key and Escape - float mouseDx = 42.0f; - float mouseDy = -17.5f; - byte mouseButtons = 0b00000101; // Left and middle buttons - float scrollDelta = 2.5f; - String text = "test command"; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertArrayEquals(keyCodes, result.keyCodes()); - assertEquals(mouseDx, result.mouseDx(), 0.0001f); - assertEquals(mouseDy, result.mouseDy(), 0.0001f); - assertEquals(mouseButtons, result.mouseButtons()); - assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); - assertEquals(text, result.text()); - } - - @Test - void fromBytes_withMaxKeyCount_parsesAllKeys() { - // Test with 255 keys (maximum for unsigned byte) - int[] keyCodes = new int[255]; - for (int i = 0; i < 255; i++) { - keyCodes[i] = i; - } - float mouseDx = 0.0f; - float mouseDy = 0.0f; - byte mouseButtons = 0; - float scrollDelta = 0.0f; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertEquals(255, result.keyCodes().length); - for (int i = 0; i < 255; i++) { - assertEquals(i, result.keyCodes()[i]); - } + buffer.putFloat(mouseDx); + buffer.putFloat(mouseDy); + buffer.put(mouseButtons); + buffer.putFloat(scrollDelta); + buffer.putShort((short) textBytes.length); + buffer.put(textBytes); + + return buffer.array(); + } + + @Test + void fromBytes_withTypicalInput_parsesCorrectly() { + int[] keyCodes = {87, 32}; // W key and Space + float mouseDx = 10.5f; + float mouseDy = -5.25f; + byte mouseButtons = 0b00000001; // Left button pressed + float scrollDelta = 1.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(mouseDx, result.mouseDx(), 0.0001f); + assertEquals(mouseDy, result.mouseDy(), 0.0001f); + assertEquals(mouseButtons, result.mouseButtons()); + assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withNoKeysPressed_parsesEmptyKeyArray() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(0, result.keyCodes().length); + } + + @Test + void fromBytes_withMultipleKeys_parsesAllKeys() { + int[] keyCodes = {87, 65, 83, 68, 340}; // WASD + Left Shift + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(5, result.keyCodes().length); + } + + @Test + void fromBytes_withNegativeMouseDeltas_parsesCorrectly() { + int[] keyCodes = {}; + float mouseDx = -100.75f; + float mouseDy = -200.5f; + byte mouseButtons = 0; + float scrollDelta = -3.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(mouseDx, result.mouseDx(), 0.0001f); + assertEquals(mouseDy, result.mouseDy(), 0.0001f); + assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); + } + + @Test + void fromBytes_withAllMouseButtonsPressed_parsesCorrectly() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = (byte) 0xFF; // All buttons pressed + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals((byte) 0xFF, result.mouseButtons()); + } + + @Test + void fromBytes_withText_parsesTextCorrectly() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = "Hello World!"; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withUnicodeText_parsesUtf8Correctly() { + int[] keyCodes = {}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = "日本語テスト 🎮"; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withCompleteInput_parsesAllFieldsCorrectly() { + int[] keyCodes = {69, 256}; // E key and Escape + float mouseDx = 42.0f; + float mouseDy = -17.5f; + byte mouseButtons = 0b00000101; // Left and middle buttons + float scrollDelta = 2.5f; + String text = "test command"; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertArrayEquals(keyCodes, result.keyCodes()); + assertEquals(mouseDx, result.mouseDx(), 0.0001f); + assertEquals(mouseDy, result.mouseDy(), 0.0001f); + assertEquals(mouseButtons, result.mouseButtons()); + assertEquals(scrollDelta, result.scrollDelta(), 0.0001f); + assertEquals(text, result.text()); + } + + @Test + void fromBytes_withMaxKeyCount_parsesAllKeys() { + // Test with 255 keys (maximum for unsigned byte) + int[] keyCodes = new int[255]; + for (int i = 0; i < 255; i++) { + keyCodes[i] = i; } - - @Test - void fromBytes_withNegativeKeyCode_parsesAsSignedShort() { - // Key codes are stored as shorts, so negative values are possible - int[] keyCodes = {-1, -100}; - float mouseDx = 0.0f; - float mouseDy = 0.0f; - byte mouseButtons = 0; - float scrollDelta = 0.0f; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertEquals(-1, result.keyCodes()[0]); - assertEquals(-100, result.keyCodes()[1]); - } - - @Test - void fromBytes_withLargeFloatValues_parsesCorrectly() { - int[] keyCodes = {}; - float mouseDx = Float.MAX_VALUE; - float mouseDy = Float.MIN_VALUE; - byte mouseButtons = 0; - float scrollDelta = Float.POSITIVE_INFINITY; - String text = ""; - - byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); - RawInput result = RawInput.fromBytes(bytes); - - assertEquals(Float.MAX_VALUE, result.mouseDx(), 0.0f); - assertEquals(Float.MIN_VALUE, result.mouseDy(), 0.0f); - assertEquals(Float.POSITIVE_INFINITY, result.scrollDelta(), 0.0f); - } - - @Test - void recordEquality_withSameValues_areEqual() { - int[] keyCodes = {65}; - RawInput input1 = new RawInput(keyCodes, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); - RawInput input2 = new RawInput(keyCodes, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); - - // Records use reference equality for arrays, so these won't be equal - // unless they share the same array reference - assertEquals(input1, input2); - } - - @Test - void recordEquality_withDifferentValues_areNotEqual() { - RawInput input1 = new RawInput(new int[] {65}, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); - RawInput input2 = new RawInput(new int[] {66}, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); - - assertNotEquals(input1, input2); + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(255, result.keyCodes().length); + for (int i = 0; i < 255; i++) { + assertEquals(i, result.keyCodes()[i]); } + } + + @Test + void fromBytes_withNegativeKeyCode_parsesAsSignedShort() { + // Key codes are stored as shorts, so negative values are possible + int[] keyCodes = {-1, -100}; + float mouseDx = 0.0f; + float mouseDy = 0.0f; + byte mouseButtons = 0; + float scrollDelta = 0.0f; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(-1, result.keyCodes()[0]); + assertEquals(-100, result.keyCodes()[1]); + } + + @Test + void fromBytes_withLargeFloatValues_parsesCorrectly() { + int[] keyCodes = {}; + float mouseDx = Float.MAX_VALUE; + float mouseDy = Float.MIN_VALUE; + byte mouseButtons = 0; + float scrollDelta = Float.POSITIVE_INFINITY; + String text = ""; + + byte[] bytes = createRawInputBytes(keyCodes, mouseDx, mouseDy, mouseButtons, scrollDelta, text); + RawInput result = RawInput.fromBytes(bytes); + + assertEquals(Float.MAX_VALUE, result.mouseDx(), 0.0f); + assertEquals(Float.MIN_VALUE, result.mouseDy(), 0.0f); + assertEquals(Float.POSITIVE_INFINITY, result.scrollDelta(), 0.0f); + } + + @Test + void recordEquality_withSameValues_areEqual() { + int[] keyCodes = {65}; + RawInput input1 = new RawInput(keyCodes, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + RawInput input2 = new RawInput(keyCodes, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + + // Records use reference equality for arrays, so these won't be equal + // unless they share the same array reference + assertEquals(input1, input2); + } + + @Test + void recordEquality_withDifferentValues_areNotEqual() { + RawInput input1 = new RawInput(new int[] {65}, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + RawInput input2 = new RawInput(new int[] {66}, 1.0f, 2.0f, (byte) 1, 0.5f, "test"); + + assertNotEquals(input1, input2); + } } diff --git a/pixi.lock b/pixi.lock index dc6d752..62750c5 100644 --- a/pixi.lock +++ b/pixi.lock @@ -33,7 +33,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312hcaba1f9_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.74.1-py312h6f3464c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.0-h6083320_0.conda @@ -227,7 +227,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312hcaba1f9_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.74.1-py312h6f3464c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.0-h6083320_0.conda @@ -444,7 +444,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312hcaba1f9_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/grpcio-1.74.1-py312h6f3464c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.3.0-h6083320_0.conda @@ -1038,17 +1038,17 @@ packages: - pkg:pypi/gmpy2?source=hash-mapping size: 214554 timestamp: 1762946924209 -- conda: https://conda.anaconda.org/conda-forge/noarch/gradle-9.2.0-h707e725_0.conda - sha256: 20dd623f094679d910110ce0ecbd9c5b1c8008b20ea0117b7656ee5e2c110f37 - md5: bc615ee587a58195f3618317ade7e8ca +- conda: https://conda.anaconda.org/conda-forge/noarch/gradle-8.11.1-h707e725_0.conda + sha256: f915593b543c4156d0d9dce05e0056975482ae25b1637cb5830efaeb4bffb78a + md5: b0d211f827e1827cafe15b4000500223 depends: - __unix - - openjdk >=17,<25 + - openjdk >=8,<23 license: Apache-2.0 license_family: APACHE purls: [] - size: 133208780 - timestamp: 1762592486483 + size: 134432899 + timestamp: 1732134820391 - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-hecca717_2.conda sha256: 25ba37da5c39697a77fce2c9a15e48cf0a84f1464ad2aafbe53d8357a9f6cc8c md5: 2cd94587f3a401ae05e03a6caf09539d @@ -2107,7 +2107,7 @@ packages: - pypi: ./ name: mineagent version: 0.0.1 - sha256: 855a96eeea99e258cc9f49e313cc217b4040768c29f9cf2618b01193bccaeb67 + sha256: 7ad354ec5447dffd0ee851e6fda1003f5764b74dae8b41bf603b45d702e698ef requires_dist: - pyyaml - dacite diff --git a/pyproject.toml b/pyproject.toml index 7017b92..b1762ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ torchvision = "*" numpy = "==1.26.4" scipy = "*" tensorboard = "*" -gradle = "*" +gradle = "<9.0.0" openjdk = "21.*"