Skip to content

Latest commit

 

History

History
406 lines (327 loc) · 16.6 KB

File metadata and controls

406 lines (327 loc) · 16.6 KB

Genesis Cargo Box Tetris

English | 简体中文

This is an interactive container-loading game built on Genesis rigid-body physics. Use WASD and J/K/L to choose the position and orientation of each cargo box, then release it from above the target. Every placed box remains a dynamic rigid body, so a new impact can push, tilt, or topple the entire stack.

The default configuration uses a native Nyx GPU window, Genesis CPU physics, and a fixed 60 Hz main loop.

Features

  • Automatically selects assets from assets/box/ whose three side lengths are all within 0.3–0.6 m, then randomizes the box sequence.
  • Moves the target freely across the container floor with WASD and rotates it by 90° around world X/Y/Z with J/K/L.
  • Resolves target height with a live OBB shape cast. Moving over an existing box places the preview on its surface instead of restricting the cursor to precomputed empty slots.
  • Adds no artificial pose gap. After confirmation, Genesis simulates gravity, friction, collisions, sleeping, and contact-island wake-up.
  • Supports a native Nyx GPU window, Genesis Viewer, headless auto-placement, and frame-time profiling.

Requirements

  • Run commands from the repository root.
  • Use an existing Genesis Python environment that already contains torch, trimesh, and the base Genesis dependencies. setup_nyx.sh installs the overlay with --no-deps and does not install those large dependencies.
  • The setup script requires the uv command-line tool.
  • Nyx mode requires an NVIDIA CUDA GPU and a Linux X11 or XWayland session.
  • Use Genesis Viewer when Nyx or CUDA is unavailable, or --no-viewer on a headless server.

Quick Start

Activate the project's existing Genesis environment and install the validated Nyx overlay once:

source /path/to/genesis-world/.venv/bin/activate
bash play/setup_nyx.sh

Replace /path/to/genesis-world with the actual local path to the Genesis repository.

The script installs the following validated versions into the Git-ignored play/.runtime/ directory without overwriting the original virtual environment:

  • genesis-world==1.3.1
  • quadrants==1.2.0
  • numpy==2.3.5
  • av==18.0.0
  • gs-nyx==0.1.4
  • gs-nyx-plugin==0.1.5

You can also select the interpreter explicitly without activating the environment first:

PYTHON_BIN=/path/to/genesis-world/.venv/bin/python \
  bash play/setup_nyx.sh

In that case, use the same interpreter to launch the game:

/path/to/genesis-world/.venv/bin/python play/box_tetris.py

Otherwise, launch from the repository root with the active environment:

python play/box_tetris.py

You do not need to rerun setup_nyx.sh on subsequent launches.

Renderer Selection

--renderer auto is the default. It falls back to Genesis Viewer when the Nyx package-version or CUDA checks fail. If Nyx has already been selected but DISPLAY, XAUTHORITY, or libX11 is unavailable, the program reports an input-window error instead of falling back automatically. Fix the graphical session, or explicitly use --renderer genesis / --no-viewer.

You can select either interactive renderer directly:

python play/box_tetris.py --renderer nyx
python play/box_tetris.py --renderer genesis

Nyx uses 1280×800 Forward rendering, SMAA, no shadows, one camera, and a native window. Frames stay on the CUDA device without cpu().numpy(), OpenCV, or Python image copies. The current Nyx API cannot reliably report the native title-bar close event, so exit with Esc or Ctrl-C in the launch terminal.

Controls

  • W / S: move the preview deeper into the container or toward the door.
  • A / D: move the preview left or right.
  • J / K / L: rotate 90° around world X / Y / Z.
  • Space: confirm the glowing green outline and release the physical box from above it.
  • Esc: exit the game.
  • I: show or hide the full key guide in Genesis Viewer only.
  • Left mouse drag: orbit the Nyx camera around the loading area.
  • Right mouse drag: pan the Nyx camera.
  • Middle mouse vertical drag: dolly the Nyx camera in or out.

WASD, J/K/L, Space, and Esc have the same meaning in both interactive renderers. The mouse orbit controller above is provided by nyx_x11_input.py; Genesis Viewer uses its own native camera interaction.

Nyx camera gestures are active only inside the focused Nyx client area and do not grab window-manager events, so the title bar can still move the window. Keyboard and mouse commands are ignored until the Nyx window has focus. Edge resize behavior depends on Nyx and the desktop window manager; the internal render resolution remains 1280×800.

WASD controls a two-dimensional cursor over the entire container floor rather than jumping among filtered candidate slots. After every move or rotation, the game performs an exact downward OBB shape cast against the live stack. The preview sits on the floor when nothing is below it and moves to the first box surface when there is support underneath. Near a wall or neighboring axis-aligned box face, it snaps to an exact face-contact coordinate; continuing in the same direction moves away from the snap point.

Command-Line Options

Run python play/box_tetris.py --help for the generated command-line help.

Option Default Purpose
--renderer {auto,nyx,genesis} auto Select the interactive renderer. auto falls back from Nyx when package-version or CUDA checks fail.
--backend {cpu,cuda} cpu Select the Genesis physics backend. CPU is usually faster for this single scene.
--hibernation {auto,on,off} auto Control dynamic-body sleeping. Auto enables it on Genesis 1.2 and later.
--no-viewer off Disable both Nyx and Genesis Viewer for servers or automated tests.
--max-boxes N 72 Number of physical boxes to preallocate at startup.
--max-collision-pairs N 512 Genesis collision-pair capacity. Increase it after an overflow warning.
--round-limit N unlimited Play only N rounds while still preallocating --max-boxes entities.
--seed N random Fix the random box sequence for reproducible runs.
--steps N unlimited Stop after at most N main-loop steps.
--auto-place off Automatically confirm each default target for regression and stress tests.
--profile off Report p50/p95/p99 for full-frame, physics, rendering, and game-logic time.
--profile-warmup-steps N 60 Number of warm-up frames excluded from profiling.
--profile-report-steps N 300 Rolling profile-report interval.

Example: use a reproducible sequence, Genesis Viewer, and 24 boxes:

python play/box_tetris.py \
  --renderer genesis \
  --max-boxes 24 \
  --seed 7

Runtime Flow

Focused-window X11 keyboard and mouse state
    → WASD / JKL command queue + Nyx orbit camera
    → AIMING / SETTLING game state machine
    → Genesis 1/60 s rigid-body step
    → batched visual-state update
    → native Nyx GPU window

Each round proceeds as follows:

  1. Rebuild current OBBs from the live stack poses in one batch and generate broad-phase AABBs. Refresh them after every move and rotation and once more before confirmation, without synchronizing on idle aiming frames.
  2. Move 0.05 m per WASD command and update an orthogonal orientation with J/K/L. Resolve the preview through an AABB broad phase followed by an exact 15-axis continuous-SAT vertical OBB shape cast. A tilted box therefore does not turn the highest corner of its AABB into a false horizontal surface. Scene topology remains unchanged at runtime.
  3. On Space, move the precreated physical box 0.8 m above the target, clear its velocity, and release it.
  4. Genesis computes gravity, contacts, and friction. A new collision wakes connected old boxes, so the stack can move, tilt, or collapse as a whole.
  5. Begin the next round only after the current contact island sleeps or the full stack remains below the velocity thresholds.

There is no magnetic attachment, welding, or conversion to static geometry after placement. Physics determines the final pose. Planning also introduces no artificial gap: target face coordinates for the box, floor, wall, or neighboring axis-aligned box are equal. The 1e-6 m geometry epsilon is used only to classify projection overlap and is never added to the pose.

This lifecycle was inspired by the fixed-step, dynamic-stack, contact-island, and sleeping strategy used by the Dexterity Foresight Truck Loading Game. The browser reference first moves the current box to its support surface with a kinematic shape cast and then makes it dynamic. This project preserves the original requirement for a complete free fall and waits for physical stability before starting the next round.

Why It Sustains 60 FPS

  • Genesis 1.3.1 enables contact islands, hibernation, and native gravity.
  • Seventy-two off-camera physics slots settle and sleep before the first round; unused slots do not participate in continuous contact solving.
  • Each cargo box has one analytic URDF <box> collider. The original GLB is used only for textured visuals.
  • At 10 Hz, the stability gate first reads the current box's sleep flag. If it is still awake or the flag is unavailable, it reads full-stack velocity in one batch instead of synchronizing every physics step.
  • Nyx consumes Genesis visual state directly and renders Forward on CUDA.
  • The green outline is one merged mesh per asset size; moving or hiding it requires a single batched pose update.
  • Default placement candidates pass through a vectorized AABB broad phase, and only plausible OBB pairs enter batched NumPy SAT. No per-candidate physics query is required, even when placed boxes are tilted.
  • The main loop uses an absolute-clock frame pacer, so a short stall does not create an unbounded catch-up loop.
  • Genesis does not print an FPS record every physics step. --profile emits periodic percentile summaries instead.

The following results are regression measurements from the development machine, not a performance guarantee for every system. With an RTX 4090, 1280×800 output, 72 precreated cargo boxes, and real drops and collisions, a Nyx stress run completed 40 placement rounds. After 60 warm-up frames, the remaining 3,540 frames produced the results below. Full-frame time includes the 60 Hz pacer wait, so 16.66 ms means the loop remained locked to the target frame rate.

Metric p50 p95 p99 max
Full frame 16.66 ms 16.66 ms 16.66 ms 16.67 ms
Active work 1.87 ms 2.99 ms 5.02 ms 12.93 ms
Genesis step 0.47 ms 0.86 ms 1.09 ms 2.31 ms
Nyx render 1.28 ms 1.91 ms 2.20 ms 3.43 ms

The number of full frames above 110% of the frame budget was 0/3540. Reproduce the Nyx test with:

python play/box_tetris.py \
  --renderer nyx \
  --max-boxes 72 \
  --round-limit 40 \
  --seed 11 \
  --auto-place \
  --steps 3600 \
  --profile

A separate complete headless run placed and stabilized all 72 boxes with the same seed. Across 7,177 post-warm-up frames, active-work p99/max was 2.75/9.89 ms and Genesis-step p99/max was 2.17/3.32 ms, with no collision-buffer overflow or abnormal exit.

The complete 72-box headless regression command is:

python play/box_tetris.py \
  --renderer genesis \
  --backend cpu \
  --no-viewer \
  --max-boxes 72 \
  --max-collision-pairs 512 \
  --seed 11 \
  --auto-place \
  --steps 10000 \
  --profile \
  --profile-report-steps 10000

Assets, Capacity, and Physics

The usable loading volume is approximately 2.35 × 3.00 × 2.35 m. The door is on the Y=0 side, and both the doorway and top remain open. At startup, the game measures the real bounds of assets/box/*.glb and uses only assets whose three side lengths all fall within 0.3–0.6 m:

  • card04_genesis.glb
  • card05_genesis.glb
  • card06_genesis.glb
  • cube05_32cm_genesis.glb
  • cube06_42cm_genesis.glb
  • cube07_52cm_genesis.glb

The sequence is random; use --seed 7 for a reproducible order. Genesis cannot add ordinary rigid bodies after scene build, so the game precreates and sleeps 72 physics slots behind the camera. Reduce capacity and the collision-pair buffer when appropriate:

python play/box_tetris.py \
  --max-boxes 24 \
  --max-collision-pairs 256

The default physics configuration uses 60 Hz, the Newton solver, sparse CPU island solving, 30 main iterations, 20 line-search iterations, and a 512-pair collision capacity. Experimental no-slip post-processing is disabled because it creates a dense buffer that grows quadratically with the number of constraints and interferes with normal sleeping.

Use --backend cuda for A/B comparison, but CPU physics is usually faster for this single interactive scene. Nyx rendering remains on the GPU either way.

Headless Check

python play/box_tetris.py \
  --renderer genesis \
  --backend cpu \
  --no-viewer \
  --max-boxes 2 \
  --max-collision-pairs 256 \
  --auto-place \
  --steps 300 \
  --profile

--no-viewer disables both Nyx and Genesis Viewer. A headless invocation without --steps executes only one main-loop step.

Project Layout

play/
├── box_tetris.py          # Scene, physics, placement planner, and main loop
├── nyx_x11_input.py       # X11 keyboard and mouse input for the Nyx window
├── preview_outline.obj    # Green Nyx target outline
├── box_urdf/              # Textured visuals and analytic box colliders
├── requirements-nyx.txt   # Validated Genesis/Nyx overlay versions
├── setup_nyx.sh           # Installs the overlay into play/.runtime/
├── README.md              # English documentation
└── README_CN.md           # Chinese documentation

play/.runtime/, __pycache__/, and native-window configuration files are generated locally, ignored by Git, and should not be committed.

Troubleshooting

Missing Python Modules at Startup

If Python reports that torch, trimesh, dill, or another base module is missing, the original Genesis environment is not active. Activate that environment before running setup_nyx.sh. The script installs only the validated overlay and does not install the large base dependencies.

If uv is missing, install it first or point UV_BIN=/path/to/uv to its executable.

Nyx, CUDA, or the Graphical Session Is Unavailable

  • No NVIDIA CUDA GPU: use --renderer genesis.
  • No desktop window: use --no-viewer for automated runs.
  • DISPLAY, XAUTHORITY, or libX11 error: run in an X11 or XWayland session. Native Wayland requires XWayland.
  • Nyx window is visible but the controls do not respond: click the window to focus it. Window discovery retries in the background and normally takes at most about one second.

Space Does Not Release the Box

The target must remain below the container ceiling and the game must be in AIMING state. If the target is too high, the terminal prints a warning; move it to a lower location. While the previous box is still moving, the game stays in SETTLING and waits for the contact island to sleep or the full stack to remain below the velocity thresholds. The terminal log reports the current state and the result of each round.

Low Frame Rate

  1. Confirm that the log reports Genesis 1.3.1 with hibernation and contact islands enabled.
  2. Prefer --backend cpu for this single scene and Nyx for interactive rendering.
  3. Check that rendering has not fallen back to software and close unrelated GPU-heavy applications.
  4. Reduce preallocation with --max-boxes 24 --max-collision-pairs 256.
  5. Add --profile to separate Genesis-step, Nyx-render, and game-logic cost.

Collision-Pair Overflow

If Genesis reports a collision-pair buffer overflow, raise --max-collision-pairs from 512 to 1024 or higher. Larger capacities use more memory.

The Nyx Title-Bar Close Button Does Not Exit

The current Nyx API cannot reliably report the native close event. Press Esc, or return to the launch terminal and press Ctrl-C. Nyx keeps an internal resolution of 1280×800; edge resizing depends on Nyx and the desktop window manager.

References