Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ the manipulator from natural language.

## Overview

This repository sits on top of the WMX ROS 2 manipulator stack and gives it
This repository sits on top of the WMX R2 manipulator stack and gives it
a higher-level reasoning layer:

- **VLM service** — FastAPI server wrapping a vLLM-hosted Gemma 4 model
Expand Down Expand Up @@ -146,8 +146,8 @@ grep '\[timing\]' baseline.log

- [movensys-manipulator](https://github.com/movensys/movensys-manipulator) — ROS 2 manipulator stack driven by this layer
- [movensys-simulation](https://github.com/movensys/movensys-simulation) — Isaac Sim scenes used by the demos
- [wmx-ros2](https://github.com/movensys/wmx-ros2) — Core WMX motion control packages
- [wmx-ros2-doc](https://github.com/movensys/wmx-ros2-doc) — WMX ROS 2 documentation site
- [wmx-r2](https://github.com/movensys/wmx-r2) — Core WMX motion control packages
- [wmx-r2-doc](https://github.com/movensys/wmx-r2-doc) — WMX R2 documentation site

## License

Expand Down
42 changes: 23 additions & 19 deletions movensys_sample/doc/1a_robopoly_simulation.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
# Running Robopoly Game
# Running Robopoly Game
## Step 1: Movensys-manipulator
check `movensys-manipulator/doc` 1_ and 2_
Run `movensys-manipulator/doc/6a_yolo_simulation.md` step 1-4
Follow 1_setup.md in `movensys-manipulator/doc` and set to `export MOVENSYS_ROS_VERSION=general` in ~/.bashrc configuration

## Step 2: Run VLM package
Run `movensys_vlm/doc/running.md`
## Step 2: Open Isaac Sim
Open `~/workspaces/movensys-simulation/<MANIPULATOR_MODEL>/7a_robopoly_simulation.usd` and press play button.

## Step 3: Running movensys_robopoly
### Step 3: Build containers on NVIDIA Blackwell GPU
```bash
cd ~/workspaces/movensys-intelligence/movensys_sample/doc
./run_robopoly.sh build_nvidia
```
export MOVENSYS_PNP_DRY_RUN=0
cd ~/workspaces/movensys-intelligence/movensys_sample/movensys_robopoly/docker
docker compose down
docker compose build
docker compose up -d
```

## Step 4: Enjoy the robopoly game
1. Click `Toggle is_YOLO` and check `is_YOLO` is set to ON.
2. Click Reset game and play the game.




## Step 4: Run simulator bridge
```
mros ros2 launch movensys_manipulator_moveit_config sim_bridge.launch.py simulator:=isaacsim use_sim_time:=true
```

## Step 5: Launch MoveIt2's OMPL + API
```
mros ros2 launch movensys_manipulator_moveit_config moveit.launch.py use_sim_time:=true
```

## Step 6: Launch Yolo detector
```
mros ros2 launch movensys_manipulator_perception yolo_dice_and_cube_detector.launch.py use_sim_time:=true
```

## Step 7: Enjoy the robopoly game
1. Click `Toggle is_YOLO` and check `is_YOLO` is set to ON.
2. Click Reset game and play the game.


# Running Robopoly Game w/o moving robot arm
Expand Down
7 changes: 5 additions & 2 deletions movensys_sample/doc/1c_robopoly_real.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
# Running Robopoly Game
## 1. Execution Procedure

### Step 1: Launch wmx-ros2 (Terminal 1)
### Step 1: Launch wmx-r2 (Terminal 1)
```bash
cd ~/workspaces/movensys-intelligence/movensys_sample/doc
./run_robopoly.sh wmx-ros2
./run_robopoly.sh wmx-r2
```




### Step 2a: Build containers on Nvidia env (Terminal 2)
```bash
cd ~/workspaces/movensys-intelligence/movensys_sample/doc
./run_robopoly.sh build_nvidia
```

### Step 2b-1: Build vllm containers on Intel env (Terminal 2)
```bash
cd ~/workspaces/movensys-intelligence/movensys_sample/doc
./run_robopoly.sh build_intel_vllm
```

### Step 2b-2: Build other containers (Terminal 3)
```bash
cd ~/workspaces/movensys-intelligence/movensys_sample/doc
./run_robopoly.sh build_intel
```

Expand Down
16 changes: 8 additions & 8 deletions movensys_sample/doc/run_robopoly.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ set -e

MODE=${1:-}
case "$MODE" in
wmx-ros2|build_nvidia|build_intel_vllm|build_intel|run) ;;
wmx-r2|build_nvidia|build_intel_vllm|build_intel|run) ;;
*)
echo "Usage: $0 {wmx-ros2|build_nvidia|build_intel_vllm|build_intel|run}" >&2
echo " wmx-ros2 Launch the wmx-ros2 manipulator driver (foreground, prompts for sudo)" >&2
echo "Usage: $0 {wmx-r2|build_nvidia|build_intel_vllm|build_intel|run}" >&2
echo " wmx-r2 Launch the wmx-r2 manipulator driver (foreground, prompts for sudo)" >&2
echo " build_nvidia Rebuild docker images and start persistent containers (NVIDIA GPU)" >&2
echo " build_intel_vllm Down all + drop caches + build/run vllm only (Intel GPU)" >&2
echo " build_intel Build remaining services: vectordb, vlm, whisper, manipulator, robopoly (Intel GPU)" >&2
echo " run Start runtime containers + ROS launches in a tmux session" >&2
echo "" >&2
echo "Recommended order (each in its own terminal):" >&2
echo " Terminal 1: $0 wmx-ros2" >&2
echo " Terminal 1: $0 wmx-r2" >&2
echo " Terminal 2: $0 build_nvidia (NVIDIA GPU, only when code/images change)" >&2
echo " Terminal 2: $0 build_intel_vllm (Intel GPU, only when code/images change)" >&2
echo " Terminal 2: $0 build_intel (Intel GPU, after build_intel_vllm)" >&2
Expand Down Expand Up @@ -133,10 +133,10 @@ if [[ "$MODE" == "build_intel" ]]; then
fi

# ============================================================================
# WMX-ROS2 MODE: foreground manipulator driver, owns its own terminal + sudo
# WMX R2 MODE: foreground manipulator driver, owns its own terminal + sudo
# ============================================================================
if [[ "$MODE" == "wmx-ros2" ]]; then
echo "==> [wmx-ros2] launching manipulator driver"
if [[ "$MODE" == "wmx-r2" ]]; then
echo "==> [wmx-r2] launching manipulator driver"
exec sudo --preserve-env=PATH \
--preserve-env=AMENT_PREFIX_PATH \
--preserve-env=COLCON_PREFIX_PATH \
Expand All @@ -149,7 +149,7 @@ if [[ "$MODE" == "wmx-ros2" ]]; then
--preserve-env=RMW_IMPLEMENTATION \
bash -c "source /opt/ros/${ROS_DISTRO}/setup.bash \
&& source ${HOME}/workspaces/movensys_ws/install/setup.bash \
&& ros2 launch wmx_ros2_package wmx_ros2_cr3a_manipulator.launch.py use_sim_time:=false"
&& ros2 launch wmx_r2_package wmx_r2_cr3a_manipulator.launch.py use_sim_time:=false"
fi

# ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion movensys_sample/movensys_robopoly/game/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
HOTEL_PRICE = 3 * TIER_PRICE # tier 3 → $300 cumulative
TAX_AMOUNT = 100 # flat tax for Non-Free Parking (§4.3)
CHANCE_AMOUNT = 200 # chance card payout magnitude (§4.4)
LAPS_TO_WIN = 2 # end-of-game lap-count cap (§6.2)
LAPS_TO_WIN = 1000 # end-of-game lap-count cap (§6.2)


def tier_of(p) -> int:
Expand Down
91 changes: 75 additions & 16 deletions movensys_sample/movensys_robopoly/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ function announceFromEvent(env) {
const t = payload.totals || {};
text = `🤝 Draw — both players at $${t.user ?? "?"}`;
} else {
const reason = payload.reason === "lap_cap" ? " (2 laps)" : "";
const reason = payload.reason === "lap_cap" ? " (1000 laps)" : "";
text = `🏆 ${payload.winner} wins the game!${reason}`;
}
announce(text, "win", { sticky: true });
Expand Down Expand Up @@ -888,6 +888,39 @@ let currentState = null;
// while the Buy modal is open waiting for a choice).
let turnInFlight = false;

// Auto-mode = robot-vs-robot. When ON, the user's turns are driven by the
// same deterministic robot logic as the robot's turns: the dice endpoint
// switches to /api/dice/roll_robot (physical pick-and-place of the die
// instead of only reading the human-thrown face), and AWAIT_DECISION is
// resolved by pickRobotDecision() instead of popping the Buy modal. The
// game then plays itself, turn after turn, until someone wins.
// Toggled by the "A" hotkey (works in both debug and game mode); ?auto=1
// enables it on load.
let autoMode = new URLSearchParams(location.search).get("auto") === "1";

function setAutoMode(on) {
autoMode = !!on;
const msg = autoMode
? "Auto-mode ON — robot vs robot"
: "Auto-mode OFF — user vs robot";
appendChat({ role: "sys", text: msg });
if (document.body.classList.contains("game-mode")) flashGameOverlay(msg);
// Keep the URL in sync so a reload preserves the mode.
const url = new URL(location.href);
if (autoMode) url.searchParams.set("auto", "1");
else url.searchParams.delete("auto");
history.replaceState(null, "", url.toString());
// Turning it on mid-turn: clear the idempotency key so the auto-driver
// can re-fire for the current step (e.g. the user is sitting idle on
// their own TURN_START), then kick it.
if (autoMode) {
vlmPlayerLastTurnKey = null;
maybeAutoTriggerRobotTurn();
}
}

function toggleAutoMode() { setAutoMode(!autoMode); }

function renderState(state) {
if (state.config && typeof state.config.is_YOLO === "boolean") {
isYOLO = state.config.is_YOLO;
Expand Down Expand Up @@ -966,9 +999,11 @@ function openStream() {
current_tier: env.payload.current_tier ?? 0,
max_tier: env.payload.max_tier,
};
// Robot decides via the VLM agent loop; keep the JS state but skip
// the modal so the operator only ever sees buy choices for the user.
if (currentState?.turn === "robot") pendingDecision = decision;
// Robot decides deterministically; keep the JS state but skip the
// modal so the operator only ever sees buy choices for the user.
// In auto-mode the user is robot-driven too, so suppress the modal
// for both players and let maybeAutoTriggerRobotTurn() decide.
if (currentState?.turn === "robot" || autoMode) pendingDecision = decision;
else showDecision(decision);
}
// Close the modal when *any* client (script, second tab, agent loop)
Expand Down Expand Up @@ -1127,7 +1162,9 @@ document.getElementById("btn-roll-dice").addEventListener("click", async () => {
// has a clear view. No pickup, no drop.
// - Robot turn → /api/dice/roll_robot. The arm physically picks
// up, lifts, drops the die, then reads the rolled face.
const diceEndpoint = (currentState && currentState.turn === "user")
// - Auto-mode → roll_robot for BOTH players: the user's turn is now
// played by the robot, so it physically rolls the die too.
const diceEndpoint = (currentState && currentState.turn === "user" && !autoMode)
? "/api/dice/read_robot"
: "/api/dice/roll_robot";
console.log("[roll-chain] dice step:",
Expand Down Expand Up @@ -1718,7 +1755,7 @@ Choice meaning (cumulative cost from unowned = rent opponent pays):
build_hotel tier 3, $300 land + hotel
skip no purchase
Upgrade delta from owned = $100 × (target_tier − current_tier).
Seed $500, GO bonus $100, tax $100, chance ±$200, 2 laps to win. Rent $150/$300/$450 per tier.
Seed $500, GO bonus $100, tax $100, chance ±$200, 1000 laps to win. Rent $150/$300/$450 per tier.

Constraints:
- decision_pending.kind=="utility" → only buy or skip are legal.
Expand Down Expand Up @@ -1950,20 +1987,21 @@ function parseVoiceDecision(text) {
// is the fallback when cash is tight. Hotels never fire from unowned
// (the two-step buy + build server txn risks a partial failure at the
// $200 boundary); they only happen on revisit.
function pickRobotDecision(state, pending) {
const liquid = state.players?.robot?.balance ?? 0;
const lap = state.lap_count?.robot ?? 0;
function pickRobotDecision(state, pending, player = "robot") {
const liquid = state.players?.[player]?.balance ?? 0;
const lap = state.lap_count?.[player] ?? 0;
const currentTier = pending.current_tier ?? 0;
const maxTier = pending.max_tier ?? 3;
const isUtility = pending.kind === "utility" || maxTier === 1;
if (isUtility) {
return (currentTier === 0 && liquid >= 200) ? "buy" : "skip";
}

// Two-player game: whoever isn't `player` is the opponent.
let oppHotels = 0, ownHotels = 0, ownTotal = 0;
for (const p of Object.values(state.properties || {})) {
if (!p.owner) continue;
if (p.owner === "robot") {
if (p.owner === player) {
ownTotal++;
if (p.has_hotel) ownHotels++;
} else if (p.has_hotel) {
Expand Down Expand Up @@ -2103,22 +2141,39 @@ const SOLO_MODE = new URLSearchParams(location.search).get("solo") === "1";
function maybeAutoTriggerRobotTurn() {
if (SOLO_MODE) return; // external driver owns the game; stand down
if (!currentState || currentState.winner) return;
if (currentState.turn !== "robot") return;
const player = currentState.turn;
// The robot always auto-plays. In auto-mode (robot-vs-robot) the user's
// turns are driven by the same logic so the whole game plays itself.
const shouldDrive = player === "robot" || (autoMode && player === "user");
if (!shouldDrive) return;
const pendingPid = pendingDecision ? pendingDecision.property_id : "";
const key = `${currentState.turn}|${currentState.fsm}|${currentState.turn_number}|${pendingPid}`;
const key = `${player}|${currentState.fsm}|${currentState.turn_number}|${pendingPid}`;
if (vlmPlayerLastTurnKey === key) return;
if (currentState.fsm === "TURN_START") {
vlmPlayerLastTurnKey = key;
vlmPlayerAct("It's your turn (robot). Roll the dice and move your cube.");
if (player === "robot") {
vlmPlayerAct("It's your turn (robot). Roll the dice and move your cube.");
} else {
// Auto-mode user turn: skip the VLM round-trip (roll_and_move is
// deterministic) and fire the same Roll-dice chain the robot uses.
// The dice endpoint switches to roll_robot because autoMode is on.
const btn = document.getElementById("btn-roll-dice");
if (btn && !btn.disabled) {
appendChat({ role: "sys", text: "Auto-mode → user rolls" });
btn.click();
}
}
} else if (currentState.fsm === "AWAIT_DECISION" && pendingDecision) {
vlmPlayerLastTurnKey = key;
// Deterministic strategy lives in pickRobotDecision — no VLM call.
// The choice is legal by construction (skip is always legal), so we
// can submitDecision directly and AWAIT_DECISION always advances.
const choice = pickRobotDecision(currentState, pendingDecision);
appendChat({ role: "sys", text: `Robot decision → ${choice}` });
// In auto-mode this also handles the user's buys, using the acting
// player's own cash / holdings.
const choice = pickRobotDecision(currentState, pendingDecision, player);
appendChat({ role: "sys", text: `Auto ${player} decision → ${choice}` });
submitDecision(choice, choice === "build" ? 1 : 0)
.catch((err) => console.warn("[robot-decide]", err));
.catch((err) => console.warn("[auto-decide]", err));
}
}

Expand Down Expand Up @@ -2632,6 +2687,10 @@ function setupHotkeys() {
const k = e.key.toLowerCase();
if (k === "escape") { closeChatOverlay(); return; }

// A: toggle auto-mode (robot vs robot). Works in both debug and game
// mode; when turned on it immediately starts driving the current turn.
if (k === "a") { e.preventDefault(); toggleAutoMode(); return; }

const isGame = document.body.classList.contains("game-mode");
// Game mode: C reveals/hides the overlay without recording. Z/X always
// act as hold-to-record hotkeys *and* additionally open the overlay
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"board_id": "final",
"tile_count": 14,
"seed_money": 500,
"seed_money": 999999999999,
"start_bonus": 100,
"physical_image": "board.png",
"layout": {
Expand Down
Loading