Skip to main content
⚡ High-throughput ViZDoom rollouts, native-vector by design. ⚡

env-ViZDoom-turbo

env-ViZDoom-turbo is a Python library for reinforcement-learning researchers who need fast, parallel ViZDoom environments. It provides a Gymnasium vector environment that can be used directly or selected as an isolated environment provider in GradLab.

Each vector lane owns an independent DoomGame. Lanes advance concurrently through ViZDoom's native API, while a bounded Rust worker pool applies max-pooling, crop, resize, grayscale conversion, frame-stack rotation, and final CHW/HWC layout in one GIL-free native call. Resize geometry and area-sampling tables are compiled once per environment instead of rebuilt per step.

Install

Install the published package from PyPI:

uv add env-vizdoom-turbo

To work from source:

git clone git@github.com:tsilva/env-ViZDoom-turbo.git
cd env-ViZDoom-turbo/turbo
uv sync --all-extras

Run Python and project commands through uv run.

Use

import gymnasium as gym
import numpy as np

env = gym.make_vec(
    "env_vizdoom_turbo:EnvViZDoomTurbo-v0",
    game="VizdoomBasic-v1",
    num_envs=16,
    num_threads=8,
    obs_resize=(84, 84),
    obs_grayscale=True,
    obs_layout="chw",
    frame_skip=4,
    frame_stack=4,
    use_restricted_actions="minimal",
)

try:
    observations, infos = env.reset(seed=7)
    actions = np.zeros(env.num_envs, dtype=np.int64)
    observations, rewards, terminated, truncated, infos = env.step(actions)

    done = terminated | truncated
    if np.any(done):
        observations, infos = env.reset(
            options={
                "reset_mask": done,
                "state_indices": np.zeros(env.num_envs, dtype=np.int32),
            }
        )
finally:
    env.close()

The module-qualified ID imports the package and registers the factory. This ID is vector-only and requires an explicit game, which can be a canonical registered Vizdoom... Gymnasium ID or a ViZDoom .cfg path. EnvViZDoomTurboVecEnv remains available for direct use, and the existing scenario-specific vector IDs remain registered for compatibility.

Crop or mask observations

The crop API matches the other turbo environments. obs_crop always contains raw-screen edge widths in (top, bottom, left, right) order. With obs_crop_mode="remove", those edges are removed before resize. With obs_crop_mode="mask", the raw geometry is preserved and those edges are replaced with obs_crop_fill before resize.

For example, the classic 320×240 Doom HUD occupies the bottom 32 pixels. This keeps the HUD enabled in ViZDoom while masking it out of policy observations:

env = EnvViZDoomTurboVecEnv(
    "VizdoomBasic-v1",
    vizdoom_config={"render_hud": True},
    obs_crop=(0, 32, 0, 0),
    obs_crop_mode="mask",
    obs_crop_fill=0,
)

The 320×240-to-84×84 grayscale area-resize profile applies crop removal and masking directly in the indexed native pipeline, without an intermediate RGB frame conversion.

Frame-aligned info histories

Pass info_frame_stack_keys to request policy-transition histories whose depth always matches the resolved frame_stack:

env = EnvViZDoomTurboVecEnv(
    "VizdoomBasic-v1",
    frame_skip=4,
    frame_stack=4,
    game_variables=["HEALTH", "ARMOR", "AMMO2", "SELECTED_WEAPON"],
    info_filter={
        "mode": "all",
        "keys": ["health", "armor", "ammo2", "selected_weapon"],
    },
    info_frame_stack_keys=["health", "armor", "ammo2", "selected_weapon"],
)

For every selected key such as health, the existing current value and mask remain infos["health"] and infos["_health"]. The opt-in history adds infos["health_frame_stack"] with shape (num_envs, frame_stack) and infos["_health_frame_stack"] with shape (num_envs,); a non-scalar signal's trailing dimensions follow the history axis unchanged. Histories are ordered oldest-to-newest. An ordinary reset repeats the reset value, a masked reset changes only selected lanes, and every vector-environment step() shifts and appends exactly once regardless of frame_skip. Terminal histories are returned before reset, and live snapshots preserve continuation histories exactly. These are policy-transition histories, not raw ViZDoom-tic histories.

Selected keys must be present in an info_filter with mode="all", available on reset and every step, and unique. Unknown, unavailable, colliding, or filtered-out selections fail during construction rather than falling back to current-only values.

Augmented environments

Augmented variants use the <base>-Plus-v<version> naming convention. VizdoomBasic-Plus-v1 preserves the canonical Basic scenario while sampling one target appearance and one coordinated wall/floor/ceiling texture set per lane on every reset:

env = EnvViZDoomTurboVecEnv(
    "VizdoomBasic-Plus-v1",
    num_envs=16,
    enemy_variants={
        "target": [
            "original",
            "basalt-furnace-sentinel-v1",
            "verdigris-ram-hound-v1",
        ],
    },
    surface_variants={
        "texture_set": [
            "original",
            "polar-bunker-v1",
            "solar-shrine-v1",
            "verdant-ruin-v1",
        ],
    },
)
observations, infos = env.reset(seed=7)
print(infos["target_variant_index"])
print(infos["texture_set_variant_index"])

Transition infos stay numeric and portable. Resolve those indices through the immutable enemy_variants and surface_variants catalogs, or use active_enemy_variant_ids() and active_surface_variant_ids() outside the transition path when human-readable IDs are needed.

Each non-original texture-set choice changes all three room surfaces together, so a lane cannot mix materials from different themes. Omitting either variant mapping samples uniformly from every catalog default.

VizdoomDefendLine-Plus-v1 preserves the canonical Defend the Line mechanics while independently selecting one configured appearance for each enemy and surface role in every vector lane on every reset:

env = EnvViZDoomTurboVecEnv(
    "VizdoomDefendLine-Plus-v1",
    num_envs=16,
    enemy_variants={
        "shooter": [
            "original",
            "basalt-furnace-sentinel-v1",
        ],
        "fighter": [
            "original",
            "verdigris-ram-hound-v1",
        ],
    },
    surface_variants={
        "wall": [
            "original",
            "basalt-blocks-v1",
            "steel-panels-v1",
        ],
        "floor": [
            "original",
            "dark-stone-v1",
        ],
        "ceiling": [
            "original",
            "industrial-grid-v1",
        ],
    },
)
observations, infos = env.reset(seed=7)
print(infos["shooter_variant_index"])
print(infos["fighter_variant_index"])
print(infos["wall_variant_index"])
print(infos["floor_variant_index"])
print(infos["ceiling_variant_index"])

Selection is uniform within each configured role, reproducible under reset(seed=...), and driven by separate role RNG streams so it does not consume gameplay, no-op, sticky-action, or another role's randomness. Omit enemy_variants and surface_variants to use every role's catalog defaults. An enemy_variants sequence remains a shorthand for configuring the shooter role only. Masked resets resample selected lanes only. enemy_variant_roles and surface_variant_roles, their read-only two-dimensional active-index arrays, and the role-keyed active_enemy_variant_ids() and active_surface_variant_ids() mappings expose the current choices.

The surface catalog also exposes three image-generated, coordinated sets through the immutable surface_variant_themes mapping: polar-bunker-v1, solar-shrine-v1, and verdant-ruin-v1. Each maps to matching wall, floor, and ceiling ids generated as individual material sources with shared theme references. Role selection remains independent, so configure each role with the corresponding singleton id when a reset must use one intact visual theme.

In a GradLab environment config, declare the same list under env_config.env_args.enemy_variants or env_config.env_args.surface_variants.

Reusable source frames, Doom patch lumps, manifests, proofs, and provenance live under env_vizdoom_turbo/assets/enemy_variants/. Seamless 64×64 PLAYPAL surface tiles, tiled proofs, prompts, manifests, and provenance live under env_vizdoom_turbo/assets/surface_variants/. The packaged Plus WADs are built from the editable scenario sources with:

uv run python scripts/build_basic_plus.py \
  --acc /absolute/path/to/acc \
  --acc-include /absolute/path/to/acc-source

uv run python scripts/build_defend_line_plus.py \
  --acc /absolute/path/to/acc \
  --acc-include /absolute/path/to/acc-source

New generated surface sources can be normalized into a compatible tile with scripts/process_surface_variant.py; its manifest gate verifies opacity, palette membership, exact dimensions, and measured wrap seams. The default pipeline center-crops one material source and downsamples it directly to 64×64, repairing only wrap axes that exceed the seam threshold. Legacy generated grids remain supported through --grid-row and --grid-column; source and processed comparison grids live with the editable Defend the Line Plus scenario sources.

Turbo Vector API v2

EnvViZDoomTurboVecEnv implements the strict Turbo Vector API v2:

  • metadata["turbo_api_version"] is 2, metadata["transition_transport"] is "numpy", and metadata["render_modes"] advertises rgb_array.
  • Immutable capabilities and signal_schema declarations describe supported features and the dtype, shape, and reset/step availability of every signal. capabilities["supports_info_frame_stack"] advertises opt-in aligned info histories, and each generated history has shape (frame_stack, *original_shape) in signal_schema.
  • buttons, action_mode, action_preset, action_table, action_meanings, and action_table_hash expose the resolved action semantics without provider-specific probing.
  • state_catalog is an immutable ordered tuple. Callers select reset states with an int32 state_indices array and inspect the read-only active indices with active_state_indices(); state sampling and lane routing remain caller-owned.
  • observation_ownership and observation_buffer_depth declare the exact lifetime of returned observations. Rendering is opt-in: with render_mode="rgb_array", render_lane(index) renders one lane, get_images() renders all lanes, and render() renders lane zero. With the default render_mode=None, the first two methods return None and get_images() returns one None entry per lane.

Use with rlab

Install this distribution in the rlab runtime, then select its provider:

environment:
  env_provider: env-vizdoom-turbo
  env_config:
    game: VizdoomBasic-v1
    state: default
    n_envs: 16
    env_args:
      num_threads: 8
      use_restricted_actions: minimal
      obs_grayscale: true
      obs_layout: chw
      frame_stack: 4
    preprocessing:
      frame_skip: 4
      max_pool_frames: true
      observation_size: 84
      obs_resize_algorithm: area
    task:
      id: identity
      action: {set: native}
      signals: {}
      events: {}
      termination: {}
      reward: {reward_mode: native}

Commands

uv sync --all-extras                                      # install project and dev dependencies
uv run pytest -q                                          # run Python and live-environment tests
uv run ruff check .                                       # lint Python
cargo fmt --check                                         # check Rust formatting
cargo clippy --all-targets --all-features -- -D warnings  # lint Rust
uv build --wheel                                          # build the distributable wheel
ENV_VIZDOOM_TURBO_PREBUILT_CORE=/path/to/vizdoom uv build --wheel
                                                             # package a validated optimized core

Install TurboBench 1.0.0:

uv tool install \
  --exclude-newer-package turbobench-cli=2026-08-12T00:00:00Z \
  turbobench-cli==1.0.0

Use its immutable vizdoom/basic-v1 profile for correctness-gated throughput comparisons and public performance claims. The repository-local benchmarks/compare_contract.py remains available for focused deterministic trace checks. The inherited examples/python/fps_test.py, examples/python/gymnasium_vect_bench.py, and tests/manual_test_performance.py scripts are single-build diagnostics only; they do not provide matched workloads, validity gates, or claim evidence.

Notes

  • Python 3.14 is supported. Release wheels target macOS ARM64 and Linux x86-64. Source builds require Rust 1.85 or newer.
  • ViZDoom 1.3.0 supplies built-in scenarios and Freedoom assets. Commercial Doom IWADs are not included; pass one with rom_path when required.
  • Autoreset is disabled. Terminal lanes retain their final observation and must be selected explicitly with a masked reset.
  • Preprocessing supports crop removal or masking, max-pooling, nearest/bilinear/area resize, grayscale or RGB, frame skip, frame stacking, and CHW or HWC layouts.
  • The native vector path supports image observations, rgb_array rendering, and one player. Recording is not supported.

Architecture

env-ViZDoom-turbo architecture

License

MIT

Metadata

Release files for env-vizdoom-turbo 1.3.0.post31

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for env-vizdoom-turbo 1.3.0.post31
File Interpreter ABI Platform
env_vizdoom_turbo-1.3.0.post31-cp314-cp314-manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ x86-64 Details
env_vizdoom_turbo-1.3.0.post31-cp314-cp314-macosx_15_0_arm64.whl CPython 3.14 CPython 3.14 macOS 15.0+ ARM64 Details

Total release size: 67.8 MB

Release files / env_vizdoom_turbo-1.3.0.post31-cp314-cp314-manylinux_2_28_x86_64.whl

Download URL env_vizdoom_turbo-1.3.0.post31-cp314-cp314-manylinux_2_28_x86_64.whl
Size 39.9 MB
Tags CPython 3.14 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
702c718975cb37fe6b728c752033d3b733b3dc9cd80903d76b79f3b1d34b2497
BLAKE2b-256 checksum
How to use checksums
b59e642e1fa2660f9e58b3177b4b6ab2da2da1ead7d18b8d308f6e222f8117f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release files / env_vizdoom_turbo-1.3.0.post31-cp314-cp314-macosx_15_0_arm64.whl

Download URL env_vizdoom_turbo-1.3.0.post31-cp314-cp314-macosx_15_0_arm64.whl
Size 27.9 MB
Tags CPython 3.14 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
a6bebeeb6fe71edcaf83f10747ec3f737c385bc405f60d9a560fe032b695057b
BLAKE2b-256 checksum
How to use checksums
2129f643bc1fc14268f3618c636b898dd119986ce8d4f38f1c0b911a335a3f77
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.3.0.post31 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page