# Endless World Explorer

An autonomous world exploration livestream powered by the Reactor Python SDK and the **hy-world** model.

The app connects to the hy-world model, sends random movement and camera actions to wander through the generated world, and streams the output to an RTMP server (YouTube Live, Twitch, or any RTMP endpoint).

Every generation cycle (60 chunks), a new starting image is automatically loaded from the `starting_images/` directory — along with its matching prompt from a sidecar `.prompt.txt` file — so the exploration keeps visiting fresh, contextually-described environments.

## Prerequisites

- Python 3.10+
- **ffmpeg** installed and in PATH
- A Reactor API key or a local Reactor instance

### Install ffmpeg

```bash
# macOS
brew install ffmpeg

# Ubuntu/Debian
sudo apt install ffmpeg

# Windows
winget install ffmpeg
```

## Setup

1. Install dependencies:

```bash
pip install -r requirements.txt
```

2. Add starting images to the `starting_images/` directory. Place any number of `.jpg`, `.png`, or `.webp` images — the app will cycle through them on each generation reset.

3. **(Recommended)** Add a prompt file for each image. For an image called `beach.jpg`, create `beach.prompt.txt` containing a text description of the scene. This prompt is sent to the model alongside the image for better generation quality.

```
starting_images/
├── mountain_lake.jpg
├── mountain_lake.prompt.txt    # "A misty valley with a flowing river..."
├── forest_path.jpg
├── forest_path.prompt.txt      # "A winding dirt path through a dense forest..."
├── ocean_coast.jpg
└── ocean_coast.prompt.txt      # "A tropical beach at golden hour sunset..."
```

If no `.prompt.txt` sidecar exists for an image, the `--prompt` CLI argument is used as fallback.

## Usage

### With a local Reactor instance

```bash
python main.py --local --rtmp-url rtmp://localhost/live/stream
```

### With Reactor Cloud

```bash
python main.py \
    --api-key YOUR_API_KEY \
    --model hy-world \
    --rtmp-url "rtmp://a.rtmp.youtube.com/live2/YOUR_STREAM_KEY"
```

### All options

```
usage: main.py [-h] [--model MODEL] [--api-key API_KEY] [--local]
               --rtmp-url RTMP_URL [--fps FPS] [--images-dir IMAGES_DIR]
               [--prompt PROMPT] [--movement-speed SPEED]
               [--rotation-speed SPEED] [--verbose]

Options:
  --model, -m          Model name (default: hy-world)
  --api-key, -k        Reactor API key
  --local, -l          Use local coordinator
  --rtmp-url, -r       RTMP URL with stream key (required)
  --fps, -f            Output frame rate (default: 30)
  --images-dir         Path to starting images directory
  --prompt             Generation prompt
  --movement-speed     WASD movement speed 0.0-1.0 (default: 0.8)
  --rotation-speed     Camera rotation speed 0.0-1.0 (default: 0.6)
  --verbose, -v        Enable debug logging
```

## How It Works

1. **Connects** to the hy-world model via the Reactor SDK (WebRTC).
2. **Sends a prompt** describing the desired world aesthetic.
3. **Randomly explores** by sending `set_keyboard_action` (WASD) and `set_camera_action` (look direction) commands at random intervals. Forward movement is heavily weighted so the camera keeps moving through the world.
4. **Streams frames** to the RTMP server via an ffmpeg subprocess (same approach as the `rtmp_app` example).
5. **Detects generation cycle resets** by monitoring the `chunk_index` in model state messages. The hy-world model generates 60 chunks per cycle, then resets. When chunk 0 is detected after chunk > 0, the app picks the next scene (image + prompt) and sends both to the model.
6. **Cycles through scenes** from the `starting_images/` directory in round-robin order, shuffling on each full pass. Each image's `.prompt.txt` sidecar is sent as the generation prompt to match the visual context.

## Customisation

### Exploration behaviour

Edit the constants near the top of `main.py`:

```python
# Weight forward movement more heavily
KEYBOARD_ACTIONS = ["w", "w", "w", "w", "a", "d", "still", "still", "s"]
CAMERA_ACTIONS = ["still", "still", "still", "left", "right", "up", "down"]

# How often actions change (seconds)
ACTION_CHANGE_INTERVAL_MIN = 2.0
ACTION_CHANGE_INTERVAL_MAX = 6.0
```

### Per-image prompts

Create a `<name>.prompt.txt` sidecar next to each image:

```bash
echo "A futuristic cyberpunk city at night, neon lights, rain" > starting_images/cyberpunk.prompt.txt
```

The prompt is automatically sent to the model whenever that image is selected. Images without a sidecar file use the `--prompt` CLI fallback.

### Default prompt

Pass `--prompt` to set the fallback prompt used when no `.prompt.txt` sidecar exists:

```bash
python main.py --local --rtmp-url rtmp://... \
    --prompt "A futuristic cyberpunk city at night, neon lights, rain"
```

### Speed

Control how fast the camera moves and turns:

```bash
python main.py --local --rtmp-url rtmp://... \
    --movement-speed 0.5 --rotation-speed 0.3
```
