A Gymnasium-compatible wildfire simulation environment with physics-informed fire spread dynamics and helicopter-based firefighting. Designed for reinforcement learning research in wildfire management and suppression strategies.
The controllable agent is a helitack firefighting helicopter. Its job is to move over the terrain and drop fire suppressant/water on burning regions to slow or extinguish the fire. In RL terms, the agent should be trained to contain the wildfire as quickly as possible while minimizing total burned area and using suppressant actions effectively.
By default, the wildfire ignites near the center of the map and expands outward over time according to the terrain, landcover, and fire spread dynamics.
- Physics-Informed Fire Dynamics: Realistic fire spread based on terrain, vegetation, wind, and elevation
- Gymnasium API: Fully compatible with modern RL frameworks (Stable-Baselines3, RLlib, etc.)
- Real-Time Visualization: Fast Pygame-based rendering at 60 FPS
- Flexible Observation Space: Dict-based observations with customizable wrappers
- Helitack Firefighting: Simulate aerial water/retardant drops
- Custom Reward Functions: Easy-to-extend reward system via wrappers
- Real Terrain Data: Includes landcover and elevation maps based on real geographic data
Legend:
- Dark Blue = Water/non-burnable areas
- Black = Unburned terrain
- Pink/Purple gradient = Fire spread prediction (scheduled ignition times)
- Orange = Currently burning
- Dark Gray = Already burnt/extinguished
- Yellow X = Helicopter position
- Blue circles = Recent helitack drop locations (fade over time)
- Python
3.11.11recommended uvfor Python environment and dependency managementpyenvoptional, if you manage Python versions that way- Node.js only if you modify the 3D viewer in
web/
uv tool run --with pip pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
firecastrl-envgit clone https://github.com/aisystems-lab/wildfire-env.git
cd wildfire-env
uv venv --python 3.11.11
uv syncIf you use pyenv, set the project interpreter first:
pyenv local tactix-3.11.11
uv venv --python "$(pyenv which python)"
uv syncThis installs the package from source in the project virtual environment and includes all runtime dependencies declared in pyproject.toml.
You do not need Node.js to use render_mode="3d" with the packaged viewer.
Only rebuild the web bundle if you change files under web/:
cd web
npm install
npm run buildRun the bundled examples with uv:
uv run python scripts/random_agent_human.py
uv run python scripts/random_agent_3d.pyimport gymnasium as gym
import firecastrl_env # Registers the environment
# Create environment
env = gym.make("firecastrl/Wildfire-env0", render_mode="human")
# Reset environment
obs, info = env.reset(seed=42)
# Run episode
done = False
truncated = False
total_reward = 0.0
while not (done or truncated):
# Sample random action (or use your RL policy)
action = env.action_space.sample()
# Step environment
obs, reward, done, truncated, info = env.step(action)
total_reward += reward
# Render (if render_mode="human")
env.render()
env.close()
print(f"Episode reward: {total_reward:.2f}")Dict with the following keys:
| Key | Type | Shape | Description |
|---|---|---|---|
cells |
Box(float32) |
(160, 240) |
Ignition time for each cell (inf for unscheduled) |
helicopter_coord |
Box(int32) |
(2,) |
Helicopter position [x, y] |
quenched_cells |
Box(float32) |
(1,) |
Number of cells extinguished this step |
Discrete(5) - Five possible actions:
| Action | Value | Description |
|---|---|---|
| Move Down | 0 |
Move helicopter down |
| Move Up | 1 |
Move helicopter up |
| Move Left | 2 |
Move helicopter left |
| Move Right | 3 |
Move helicopter right |
| Helitack | 4 |
Drop water/retardant in radius around helicopter |
The default reward encourages fire suppression:
- +5.0 per cell extinguished by helitack
- -0.05 per cell currently burning (continuous penalty)
- -0.01 per time step (encourages faster containment)
- -1.0 for performing helitack on non-burnable/burnt cells
- Terminated: All fires extinguished (
cells_burning == 0) - Truncated: Maximum timesteps reached (default: 2000)
Each step() returns an info dict with:
cells_burning: Current count of burning cellscells_burnt: Total cells that have burntsimulation_time: Elapsed simulation time
env = gym.make("firecastrl/Wildfire-env0", render_mode="human")Opens a Pygame window with real-time visualization at 60 FPS.
env = gym.make("firecastrl/Wildfire-env0", render_mode="3d")
env.reset(seed=42)
env.render()Launches the packaged standalone Tactics browser viewer and streams terrain and fire state directly from that exact WildfireEnv instance into the browser. The viewer is served from packaged static assets in firecastrl_env/web_dist.
Example 3D viewer outputs:
In the 3D viewer:
- The wildfire starts near the center of the map and expands outward as the simulation progresses.
- Grey regions represent concrete or city/urban areas.
- Blue regions represent water bodies.
- Other terrain colors are shades of green based on the MODIS land-cover legend, depicting different forest and vegetation types.
You can also override the viewer host and port when integrating into another app:
env = gym.make(
"firecastrl/Wildfire-env0",
render_mode="3d",
viewer_host="127.0.0.1",
viewer_port=8765,
auto_open_3d_viewer=True,
)env = gym.make("firecastrl/Wildfire-env0", render_mode="rgb_array")
rgb_frame = env.render() # Returns numpy array (H, W, 3)Returns RGB arrays suitable for video recording or analysis.
- If the 3D browser viewer does not open automatically, copy the local URL printed by the environment into your browser.
- If port
8765is already in use, pass a differentviewer_portwhen creating the environment. - If you changed files in
web/and the viewer looks stale, rebuild withcd web && npm run build. - If
humanrendering fails with apygameimport error, rerunuv sync.
FirecastRL provides powerful wrappers to customize observations and rewards:
Adds detailed cell-level features to observations, replacing the basic cells array with a multi-channel tensor.
Available Properties:
ignition_time- Time when cell will ignitespread_rate- Fire spread rateburn_time- Total burn durationfire_state- Current fire state (Unburnt/Burning/Burnt)is_river- Non-burnable water cellsis_unburnt_island- Isolated unburnt regionshelitack_drops- Number of times helitack performed on cellelevation- Terrain elevationzone- Vegetation/landcover zonevegetation- Vegetation typedrought- Drought indexposition- Normalized X,Y coordinates
Usage:
from firecastrl_env.wrappers import CellObservationWrapper
# Use default properties (most common features)
env = gym.make("firecastrl/Wildfire-env0")
env = CellObservationWrapper(env)
# Observation now includes 'detailed_cells' with shape (6, 160, 240)
# 6 channels: ignition_time, spread_rate, fire_state, is_river, helitack_drops, elevation
# Custom properties
env = CellObservationWrapper(
env,
properties=['ignition_time', 'fire_state', 'elevation', 'position'],
remove_basic_cells=True # Remove redundant 'cells' key
)
# All properties
env = CellObservationWrapper(env, properties='all')Benefits:
- Richer state representation for deep RL
- CNN-friendly multi-channel format
- Normalized values in
[0, 1]range - Flexible feature selection
Override the default reward function with your own logic.
Usage:
from firecastrl_env.wrappers import CustomRewardWrapper
def my_reward_function(env, prev_state, curr_state):
"""
Custom reward based on state changes.
Args:
env: The environment instance
prev_state: Dict with keys: cells_burning, cells_burnt, helicopter_coord, quenched_cells
curr_state: Dict with same keys as prev_state
Returns:
float: Custom reward value
"""
# Example: Heavily penalize fire spread, reward containment
delta_burning = curr_state['cells_burning'] - prev_state['cells_burning']
reward = -2.0 * delta_burning # Penalty for new ignitions
reward += 10.0 * curr_state['quenched_cells'] # Bonus for extinguishing
return float(reward)
env = gym.make("firecastrl/Wildfire-env0")
env = CustomRewardWrapper(env, reward_fn=my_reward_function)Default Reward Function:
If no reward_fn is provided, uses a built-in default that balances:
- Extinguishing fires (+)
- Preventing spread (-)
- Time efficiency (-)
Wrappers can be stacked:
from firecastrl_env.wrappers import CellObservationWrapper, CustomRewardWrapper
env = gym.make("firecastrl/Wildfire-env0")
# Add detailed observations
env = CellObservationWrapper(
env,
properties=['ignition_time', 'fire_state', 'elevation', 'spread_rate']
)
# Add custom reward
env = CustomRewardWrapper(env, reward_fn=my_reward_function)
# Now ready for training!
obs, info = env.reset()stable-baselines3 is not installed by default with the base environment. Install it first if you want to run the example below:
uv add stable-baselines3import gymnasium as gym
from stable_baselines3 import PPO
from stable_baselines3.common.env_checker import check_env
import firecastrl_env
# Create and verify environment
env = gym.make("firecastrl/Wildfire-env0")
check_env(env, warn=True)
# Train PPO agent
model = PPO(
"MultiInputPolicy", # For Dict observation spaces
env,
verbose=1,
tensorboard_log="./firecast_tensorboard/"
)
model.learn(total_timesteps=100_000)
model.save("firecast_ppo_agent")
# Evaluate trained agent
obs, info = env.reset()
for _ in range(500):
action, _states = model.predict(obs, deterministic=True)
obs, reward, done, truncated, info = env.step(action)
env.render()
if done or truncated:
break
env.close()Key parameters can be modified in firecastrl_env/envs/config.py:
| Parameter | Default | Description |
|---|---|---|
gridWidth |
240 | Grid width (cells) |
gridHeight |
160 | Grid height (cells) |
cellSize |
500 | Cell size (feet) |
MAX_TIMESTEPS |
2000 | Episode truncation limit |
HELICOPTER_SPEED |
3 | Cells moved per action |
helitackDropRadius |
2640 | Effective helitack drop radius (feet) |
firecastrl_env/
├── __init__.py # Environment registration
├── envs/
│ ├── __init__.py
│ ├── wildfire_env.py # Main Gymnasium environment
│ ├── config.py # Configuration parameters
│ ├── environment/ # Core simulation components
│ │ ├── cell.py # Grid cell representation
│ │ ├── enums.py # Fire states, burn indices, etc.
│ │ ├── helper.py # Utility functions
│ │ ├── vector.py # 2D vector math
│ │ ├── wind.py # Wind modeling
│ │ └── zone.py # Vegetation zones
│ ├── fire_engine/ # Fire physics engine
│ │ ├── fire_engine.py # Fire spread simulation
│ │ ├── fire_spread_rate.py # Spread rate calculations
│ │ └── utils.py # Fire engine utilities
│ └── training_environments/ # Terrain data
│ ├── landcover_1.png # Real landcover map
│ └── heightmap_1.png # Real elevation data
├── viewer/
│ ├── __init__.py
│ └── server.py # Static file and websocket server for 3D mode
├── web_dist/ # Packaged 3D viewer assets
│ ├── index.html
│ └── assets/
└── wrappers/ # Custom Gymnasium wrappers
├── __init__.py
├── full_cells_observation.py # Detailed cell features
├── custom_reward.py # Custom reward functions
└── clip_reward.py # Reward clipping utility
scripts/
├── random_agent_human.py # Random policy runner for Pygame mode
└── random_agent_3d.py # Random policy runner for 3D browser mode
If you use FirecastRL in your research, please cite:
@software{firecastrl2025,
title={Spatiotemporal Wildfire Prediction and Reinforcement Learning for Helitack Suppression},
author={Shaurya Mathur, Shreyas Bellary Manjunath, Nitin Kulkarni, Alina Vereshchaka},
year={2025},
url={https://sites.google.com/view/firecastrl},
doi={10.48550/arXiv.2601.14238}
}Paper DOI: https://doi.org/10.48550/arXiv.2601.14238
Contributions are welcome! Please feel free to submit a Pull Request. For major changes:
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Shaurya Mathur - shauryamathur2001@gmail.com Shreyas Bellary Manjunath - sbellary@buffalo.edu
Project Link: https://github.com/aisystems-lab/firecast-rl



