Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a0a2db6
Add Fight Caves environment with flat PufferLib 4.0 layout
jordanbailey00 Sep 7, 2026
70dcb90
Keep Fight Caves build tooling environment-local
jordanbailey00 Sep 7, 2026
4641910
Add equipment switching and RuneC viewer interactions
jordanbailey00 Sep 10, 2026
1021be3
Include optional Docker setup in Fight Caves PR
jordanbailey00 Sep 10, 2026
c9e5f84
Trim redundant Docker setup explanations
jordanbailey00 Sep 10, 2026
d7088d7
Link published Fight Caves v3 assets
jordanbailey00 Sep 10, 2026
d44f826
Use standard Puffer workflows for Fight Caves play and evaluation
jordanbailey00 Sep 10, 2026
87bada6
Merge pull request #9 from jordanbailey00/fight-caves-4.0.3
jordanbailey00 Sep 11, 2026
6015cbf
Use official PufferTank 4.0 for Fight Caves setup
jordanbailey00 Sep 11, 2026
d99ee0d
Merge pull request #10 from jordanbailey00/fight-caves-puffertank-4.0
jordanbailey00 Sep 11, 2026
c24ff7e
Document normal graphics driver selection in PufferTank
jordanbailey00 Sep 11, 2026
a648850
Highlight PufferTank software-rendering workaround
jordanbailey00 Sep 11, 2026
eb855a8
Prepare current Fight Caves integration for 4.0.5 practice PR
jordanbailey00 Sep 11, 2026
94dcdcd
Merge pull request #12 from jordanbailey00/fight-caves-4.0.5
jordanbailey00 Sep 11, 2026
557f611
Factor Fight Caves wave spawn rotation tables
jordanbailey00 Sep 11, 2026
a42881f
Derive Fight Caves loadouts from item definitions
jordanbailey00 Sep 11, 2026
eaa3c77
Keep only the requested Fight Caves episode analytics
jordanbailey00 Sep 11, 2026
9679c22
Merge pull request #13 from jordanbailey00/fight-caves-4.0.6
jordanbailey00 Sep 12, 2026
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,6 @@ resources/drive/binaries/*

vendor/nle/
vendor/fast-nle/

# Fight Caves standalone executable (standard --fast/--local build).
/fight_caves
5 changes: 5 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ elif [ "$ENV" = "nethack" ]; then
INCLUDES+=(-I./$NLE_DIR/include
-I./$NLE_DIR/build/_deps/deboost_context-src/include)
EXTRA_LDFLAGS+=(-L"$NETHACK_LIB_DIR" -lnethack -Wl,-rpath,"$NETHACK_LIB_DIR" -ldl)
elif [ "$ENV" = "fight_caves" ]; then
SRC_DIR="ocean/$ENV"
# The standard build also prepares the full viewer for puffer eval.
# Verified bundles are reused; no setup command or runtime download.
python3 "$SRC_DIR/tools.py" setup --all
elif [ -d "ocean/$ENV" ]; then
SRC_DIR="ocean/$ENV"
else
Expand Down
93 changes: 93 additions & 0 deletions config/fight_caves.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
[base]
env_name = fight_caves
checkpoint_interval = 50
eval_episodes = 10000
cudagraphs = 10
reset_state = True
seed = 73

# The trainer architecture, hyperparameters, 750M-step budget, and seed remain
# pinned to sweep winner 1nvvx5qu and deterministic retrain 8oivozuq. The live
# environment includes the approved OSRS movement/LOS parity work and the
# zero-danger-prayer-reward setting evaluated by W&B run txqsiahp.
[env]
initial_sharks = 0
initial_prayer_doses = 0
w_damage_dealt = 0.0
w_progress = 0.001
w_damage_taken = -0.25
w_npc_kill = 0.0
w_wave_clear = 0.0
w_jad_kill = 0.0
w_cave_complete = 1.0
w_player_death = -1.0
w_correct_jad_prayer = 0.0
w_correct_danger_prayer = 0.0
w_prayer_lost = -0.02
w_invalid_action = -0.1
w_tick_penalty = -0.0001
shape_unnecessary_prayer_penalty = 0.0
shape_wave_stall_start = 0
shape_wave_stall_ramp_interval = 0
shape_wave_stall_base_penalty = 0.0
shape_wave_stall_cap = 0.0
shape_jad_heal_penalty = 0.0
shape_npc_heal_penalty = -0.005
shape_no_progress_start_1 = 800
shape_no_progress_start_2 = 1600
shape_no_progress_start_3 = 2400
shape_no_progress_penalty_1 = -0.001
shape_no_progress_penalty_2 = -0.005
shape_no_progress_penalty_3 = -0.02
shape_no_attack_start = 50
shape_no_attack_base_penalty = -0.005
shape_no_attack_wave_scale = 0.05
obs_ablate_npc_distance = 0
obs_ablate_incoming_aggregates = 1
obs_ablate_npc_valid = 0

[vec]
total_agents = 4096
num_buffers = 2
num_threads = 16

[train]
gpus = 1
total_timesteps = 750_000_000
anneal_lr = 0
learning_rate = 0.00207567504650331
ent_coef = 0.000625460620549345
gamma = 0.9991261141073255
gae_lambda = 0.9
horizon = 256
minibatch_size = 32768
replay_ratio = 2.055184291514704
clip_coef = 0.05
vf_coef = 0.9336215311545304
vf_clip_coef = 0.16791546282962394
max_grad_norm = 0.1418276517190492
vtrace_rho_clip = 2.0
vtrace_c_clip = 0.9746667741536915
prio_alpha = 0.9110743956381228
prio_beta0 = 0.2258134371255269
beta1 = 0.9832670364021693
beta2 = 0.9995810484472892
eps = 1e-10

[policy]
hidden_size = 512
num_layers = 3
expansion_factor = 1

[run]
manifest_path = ''
manifest_schema_version = 2
observation_version = 'fight_caves_puffer_policy_obs_v9_run_energy_prayer_timing_mask8_no_supplies'
action_version = 'fight_caves_multidiscrete_3_head_no_supplies_v4_run_energy_prayer8_stationary_attack_tick'
reward_version = 'fight_caves_v4_progress_npc_heal_penalty_m0005_prayer_snapshot_flick_drain'
reward_clip_enabled = 1
reward_clip_min = -1.0
reward_clip_max = 1.0

[sweep]
metric = jad_kill_rate
48 changes: 48 additions & 0 deletions ocean/fight_caves/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.20)
project(fight_caves_viewer C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
get_filename_component(PUFFERLIB_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)

if(NOT RAYLIB_ROOT)
if(APPLE)
set(RAYLIB_ROOT "${PUFFERLIB_ROOT}/raylib-5.5_macos")
else()
set(RAYLIB_ROOT "${PUFFERLIB_ROOT}/raylib-5.5_linux_amd64")
endif()
endif()

set(RAYLIB_ROOT "${RAYLIB_ROOT}" CACHE PATH "Raylib distribution root")
if(NOT EXISTS "${RAYLIB_ROOT}/include/raylib.h" OR
NOT EXISTS "${RAYLIB_ROOT}/lib/libraylib.a")
message(FATAL_ERROR "Raylib unavailable at ${RAYLIB_ROOT}. Run ./build.sh fight_caves --fast")
endif()

add_executable(fc_viewer viewer.c)
target_include_directories(fc_viewer PRIVATE "${RAYLIB_ROOT}/include")
target_link_libraries(fc_viewer PRIVATE "${RAYLIB_ROOT}/lib/libraylib.a" m pthread)
set(FC_ACTIVE_LOADOUT "" CACHE STRING "Optional Fight Caves active loadout override")
if(FC_ACTIVE_LOADOUT)
target_compile_definitions(fc_viewer PRIVATE FC_ACTIVE_LOADOUT=${FC_ACTIVE_LOADOUT})
endif()
if(APPLE)
target_link_libraries(fc_viewer PRIVATE
"-framework Cocoa" "-framework IOKit" "-framework CoreVideo" "-framework OpenGL")
else()
find_package(X11 REQUIRED)
target_link_libraries(fc_viewer PRIVATE dl ${X11_LIBRARIES} GL)
endif()

# Explicit graphical regression target; ordinary viewer builds do not run it.
add_executable(fc_viewer_tests EXCLUDE_FROM_ALL "${PUFFERLIB_ROOT}/tests/fight_caves.c")
target_compile_definitions(fc_viewer_tests PRIVATE FC_VIEWER_TEST)
target_include_directories(fc_viewer_tests PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}" "${RAYLIB_ROOT}/include")
get_target_property(FC_VIEWER_TEST_LIBRARIES fc_viewer LINK_LIBRARIES)
target_link_libraries(fc_viewer_tests PRIVATE ${FC_VIEWER_TEST_LIBRARIES})

add_executable(fc_integration_tests EXCLUDE_FROM_ALL
"${PUFFERLIB_ROOT}/tests/fight_caves_integration.c")
target_include_directories(fc_integration_tests PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}" "${RAYLIB_ROOT}/include")
target_link_libraries(fc_integration_tests PRIVATE ${FC_VIEWER_TEST_LIBRARIES})
169 changes: 169 additions & 0 deletions ocean/fight_caves/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Fight Caves

A native C Fight Caves environment for PufferLib 4.0, with all 63 waves,
training, a playable Raylib viewer, and checkpoint replay. The trainer and
viewer share the same simulation.

## Setup in PufferTank 4.0

Use the official [PufferTank 4.0 environment](https://github.com/PufferAI/PufferTank/tree/4.0),
following [Puffer's installation instructions](https://puffer.ai/docs.html#installation).
Fight Caves does not have its own Docker image or Python/CUDA dependency stack.
For play and replay, start PufferTank with the display forwarding described by
Puffer; a headless container can train, but cannot show an interactive window.

> **Important: a very laggy viewer may be rendering on the CPU, even when a GPU is available.**
> PufferTank 4.0 forces the Mesa GLX driver in its shell startup. On some systems
> this selects software rendering (`llvmpipe`) instead of the available GPU.
> **Before play or replay, run the command below in each new container shell.**
> Removing this override is the first fix to try and resolved the problem in
> our PufferTank test. No rebuild or extra package installation is required.

```bash
unset __GLX_VENDOR_LIBRARY_NAME
```

This restores automatic graphics-driver selection; it does not force NVIDIA or
any particular GPU. Mesa can also render on GPUs, so it is not inherently a CPU
renderer. The command cannot fix missing GPU drivers or container graphics
access. In the playable viewer's startup output, check the `Renderer` line:
`llvmpipe` means CPU rendering. Working CUDA training alone does not prove that
the viewer is using the GPU.

Inside PufferTank's interactive shell, use its already-activated Python
environment. Until this PR is merged, clone its source branch below. After
merging, use `--branch 4.0.5` instead; the base branch has no Fight Caves code
before the merge.

```bash
git clone --branch fight-caves-4.0.5 https://github.com/jordanbailey00/PufferLib.git PufferLib-fight-caves
cd PufferLib-fight-caves
uv pip install --no-deps -e .
```

The editable installation points the existing `puffer` command at this checkout.
`--no-deps` preserves the dependencies supplied by PufferTank. Do not create
another virtual environment or replace its PyTorch/CUDA packages for Fight Caves.
Run the following commands from this checkout's root.

## Train

```bash
./build.sh fight_caves
puffer train fight_caves
```

The build automatically downloads and verifies all required assets. There is
no separate setup or viewer-build command. `config/fight_caves.ini` supplies the
750M-step configuration. For W&B logging, run `wandb login` once and train with:

```bash
puffer train fight_caves --wandb --wandb-project fight-caves
```

Ordinary training is headless; it does not create a graphical window.

Episode analytics are limited to `zero_progress_ticks`, `wave_reached`,
`wrong_prayer_hits`, `reached_wave_63`, `jad_kill_rate`, `prayer_uptime_range`,
`prayer_uptime_melee`, `prayer_uptime_magic`, `npc_healing_total`,
`jad_healing_total`, and `episode_length`. Puffer reports episode averages under
`env/`, plus its required `env/n` episode count. Prayer uptime is a fraction of
episode ticks; healing totals count effective HP restored in simulation units.
Zero-progress ticks exclude both positive progress and healing-driven negative
progress. Wrong-prayer hits require an active, incorrect protection prayer.
Generic trainer statistics such as losses and SPS are unchanged.

## Play manually

```bash
./build.sh fight_caves --fast
unset __GLX_VENDOR_LIBRARY_NAME
./fight_caves
```

The `unset` command removes PufferTank's forced Mesa selection and lets the
display choose its normal graphics driver. It does not assume a GPU brand.
Run it in each new container shell before play or replay; it requires no rebuild.

You can play without training a policy first. Press Space to start or pause,
Right Arrow to advance one tick, O to toggle debug overlays, and Q to quit.
Right-drag rotates the camera; the mouse wheel zooms. Use `--local` instead of
`--fast` for Puffer's debug/sanitizer build.

The full viewer includes tile clicks and route previews, equipment switching,
inventory and prayer controls, right-click menus, minimap/run-energy controls,
animations, projectiles, impacts, health bars, and hitsplats. The console has
wave/target/TPS selection, god mode, observations, rewards, and an event log.

## Replay a checkpoint

With the same backend and policy architecture used for training:

```bash
unset __GLX_VENDOR_LIBRARY_NAME
puffer eval fight_caves --load-model-path latest
```

Replace `latest` with a checkpoint path to select a specific model. `latest`
selects the newest file, not the highest-scoring policy. Evaluation opens the
same full viewer, using Puffer's policy inference and environment stepping.
Camera, debug, pause, and speed controls remain available; gameplay-changing
controls are disabled. Keys 1/2/4/0 select 1x/2x/4x/10x. Q closes evaluation.

Puffer compiles one selected environment/backend into `pufferlib/_C`. Rebuild
when switching environments or between native CUDA and CPU backends. Building
the standalone viewer with `--fast` does not replace the training backend.

## Assets

The first build installs the pinned [Fight Caves v3 bundles](https://github.com/jordanbailey00/fc-rl/releases/tag/fight-caves-assets-v3):

- `resources/fight_caves/runtime/`: collision, movement, and line-of-sight maps.
- `resources/fight_caves/viewer/`: models, equipment parts, animations, terrain,
textures, UI sprites/fonts, and the minimap raster.

Archive and individual-file sizes/SHA-256 hashes are checked before installation.
Valid assets are reused, including offline. Rerunning the build repairs missing
or corrupt bundles; download or verification failure stops the build. Runtime
loads local files only, without another repository or raw OSRS cache. Missing
required data produces an error, not an open-map or reduced-graphics fallback.

Optional one-time installation or verification:

```bash
python ocean/fight_caves/tools.py setup --all
python ocean/fight_caves/tools.py setup --all --verify-only
```

OSRS assets are distributed separately and are not covered by PufferLib's
software license; see `resources/fight_caves/ASSET_NOTICE.md`.

## Optional tools

Puffer's CPU/PyTorch path uses `./build.sh fight_caves --cpu` followed by
`puffer train fight_caves --slowly` or `puffer eval fight_caves --slowly`.
Native CUDA and PyTorch checkpoints use different formats. To replay native
CUDA weights on the CPU, or stop after one episode, the compatibility tool is
still available:

```bash
./build.sh fight_caves --cpu
./build.sh fight_caves --fast
python ocean/fight_caves/tools.py eval --ckpt /path/to/checkpoint.bin --episodes 1
```

It uses the same viewer executable. Rebuild the CUDA backend before returning
to native training. `./fight_caves --benchmark` runs the headless benchmark.

Maintainer tests are explicitly invoked, not part of normal setup:

```bash
bash tests/fight_caves.sh test --core
bash tests/fight_caves.sh test --all
```

Tests additionally need pytest; graphical regression builds use CMake and an
existing DISPLAY or Xvfb. These are test tools, not extra gameplay dependencies.
`test --all` builds the CPU backend; rebuild CUDA afterward for native training.
`clean-clone` tests a committed branch, while `checkout` tests an isolated source
copy. Neither command is required to use the environment.
Loading