Skip to content

About

Reproducible RL benchmark on Procgen - 5-seed, 100k-step comparisons of visual architectures, world models, exploration bonuses, offline RL and SMAX multi-agent, plus a JAX/Flax port of the same protocol.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

75 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Systematic Benchmark of Visual Architectures, World Models, and Exploration in Procgen — A Study with 5 Seeds, 5 Games, and 100k Steps

Python 3.10.11 + Procgen 0.10.7 + Stable-Baselines3 2.9.0 + PyTorch 2.5.1+cu121 (RTX 4070) — py -3.10 (Python 3.10.11)

Abstract. Systematic evaluation of 16 architectures across 6 families (CNN vs Attention vs World Models vs Augment vs New Archs vs Exploration) with 5 seeds (42-46), 5 games (bossfight, starpilot, dodgeball, maze, heist + coinrun control), and two difficulty settings (easy 200 / hard 200 / eval 0) in Procgen (~300 FPS on cuda, 50k in ~3 min). All experiments use frame_stack=1 (3×64×64 CHW uint8), PPO (lr 3e-4, n_steps 256, batch 64, n_epochs 3, γ 0.99, λ 0.95, clip 0.2), and tensorboard for training monitoring. Evaluation initially used 10 episodes and was systematically hardened throughout the study up to the definitive protocol: 100 eps stochastic + deterministic on unseen levels (seed+1000) — sections 3.10→3.12.

0. The 5 Main Conclusions of the Study

  1. The evaluation protocol dictates conclusions — and 10 episodes are insufficient. Each protocol upgrade (10→30→100 eps, unseen levels with seed+1000, dual stochastic+deterministic evaluation) reshuffled rankings: the global leader dropped (spatial 1.54 → 1.35), ICM lost its lead in maze/heist observed at 30 eps (spurious variance), and vit collapsed in dodgeball. An enduring lesson: in Procgen, rankings evaluated with <100 episodes are unreliable (sections 3.10–3.12).
  2. There is no absolute winner — there is a consistent leader: mlp_vector. Under the definitive protocol, the global top-5 is statistically indistinguishable (mlp_vector 1.43 ≈ resnet18 1.34 ≈ spatial 1.28 ≈ lstm_attention 1.26 ≈ aug_crop 1.25), but only mlp_vector (MLP over 16×16 grayscale, 256D) remained at the top across all protocols (1.25@10 → 1.36@30 → 1.43@100) and wins in starpilot. Per-game winners: aug_crop (bossfight), mlp_vector (starpilot), resnet18 (dodgeball) (section 3.12).
  3. World Models and exploration: contextual conclusions, not universal. WMs underperform only in bossfight (<0.5); in starpilot/dodgeball they match standard CNNs — the initial "WM is weak" claim was an artifact dominated by a single environment. Curiosity methods (ICM/RND/NGU) tie with standard PPO in maze/heist (re-measured across 40 runs on 24/09/2026) because an unnormalized bonus injects $\sim 10^{-7}$ reward per step, seven orders below the sparse $0/1$ extrinsic reward — and that null is reversible: re-training the same arms with the bonus rescaled to $O(\beta)$ ($2.5$–$9.5\times10^{-3}$/step, 27/09/2026) puts every scaled arm above the control ($g$ 1.70–3.94, all three heist arms surviving Bonferroni over the six comparisons). The reversal holds under stochastic execution only — the deterministic readout of the same weights stays in the same near-zero band as before (sections 3.6, 3.6.1, 3.12).
  4. At this budget, architecture matters less than rigorous evaluation — and more budget does not resolve differences. Budget scaling (100k→250k→500k) reveals stagnant learning curves for the top two configurations in their respective games: a 5× budget neither created nor eliminated an advantage, and the generalization gap remained ≈0 (no memorization up to 500k). With 5 seeds, differences across configurations are on the order of noise: the study reports trends with confidence intervals, not definitive champions (sections 3.8, 3.13). Section 21 quantifies the same two facts from the opposite direction: symbolic regression recovers a within-game slope of only $+0.08$ to $+0.12$ return per decade of budget in bossfight/dodgeball, and the measured return identifies the generating architecture family at 0.133 accuracy against a chance level of 0.111–0.167.
  5. In HRL, the primary driver is temporal abstraction — and learned skills only win when timing matters. In jumper, action-repeat (skip4) yields 4× the return of flat RL, and hierarchy with fixed skills adds nothing further; in plunder, only hierarchy with learned skills wins (4.16 vs 3.53, +18%) and yields a deterministically exploitable policy (det 2.70 vs ≤1.32). Whether "hierarchy helps" is strictly game-dependent (section 11.1).

1. Methodology

1.1. Environments

  • Wrapper procgen_wrapper.py:6 ProcgenGymWrapper(gymn.Env) — converts gym 0.26.2 legacy API (obs, done) → gymnasium 1.3.0 (obs, terminated, truncated), HWC 64×64×3 → CHW 3×64×64 (np.transpose), Discrete(15) (bossfight, starpilot, dodgeball, coinrun). Vision-free variant procgen_wrapper.py:55 ProcgenVectorWrapper (16×16 grayscale → 256D MLP) for control comparison: same game with/without computer vision.
  • Factory procgen_wrapper.py:85 make_procgen_env(game, num_levels, distribution_mode, rand_seed, frame_stack, vector) — gym.make(f'procgen:procgen-{game}-v0', num_levels, distribution_mode, rand_seed).
  • Training: num_levels=200, distribution_mode='easy' (Procgen standard); Eval: num_levels=0 (unlimited, never-before-seen levels) with seed+1000 — measures generalization.
  • No Frame Stacking: frame_stack=1, Box(3,64,64) uint8 across all benchmarks (procgen_wrapper.py:21). frame_stack=4 (12×64×64) is supported (procgen_wrapper.py:19) but unused; Imitation-player uses 128×128×4.

1.2. Architectures

  • Classic CNN models/sb3_extractors.py:8 ClassicCNNExtractor — Conv 32 8×8 s4 → 64 4×4 s2 → 64 3×3 s1 → Flatten → FC 512 (600k params), auto-detects HWC/CHW (is_hwc).
  • Attention CNN models/sb3_extractors.py:63 AttentionCNNExtractor(use_cbam) — CBAM (ChannelAttention reduction 16 + SpatialAttention kernel 7) models/cnn_attention.py:82 or purely SpatialAttentionModule models/cnn_attention.py:6 (x * attention_map + x with residual connection models/cnn_attention.py:37 to stabilize pure spatial attention, which collapsed to 0.00 deterministically). FC 512.
  • World Models & Auxiliary Visual Representations models/world_model_extractors.py:6 — VAEExtractor(latent 128, KL) + dream() deconv, deterministic AEExtractor + dream(), ReconExtractor (L2 dec 3×64×64) + dream(), ContrastiveExtractor (InfoNCE noise 0.01 + proj 64). Theoretical Note: In the root SB3 study, these extractors function as Self-Supervised Auxiliary Visual Representation Learning heads co-optimized with PPO policy gradients over real environment trajectories. Full Model-Based Imagination RL (latent RSSM transition dynamics, latent imagination rollouts, and actor-critic learning inside imagined dynamics) is implemented and benchmarked in jax_port/dreamer.py and jax_port/train_dreamer.py.
  • Contrastive Augmentations compare_augment_contrastive.py:14 ContrastiveCrop (pad 4 + random 64), ContrastiveColor (brightness 0.8-1.2), ContrastiveNoise (noise 0.01).
  • New Architectures models/combined_extractors.py — ImpalaCNNExtractor (stack of convolutional ImpalaBlock modules), ImpoolaCNNExtractor (GAP 64D), LSTMAttentionExtractor (CNN + LSTM 256 + attention), ViTExtractor (64 patches 16×16 + 4-layer Transformer), ResNet18Extractor — all with FC 512 (benchmark #6).
  • Exploration (ICM/RND/NGU) compare_maze_heist.py:16 — ICMWrapper (intrinsic bonus via forward dynamics prediction error), RNDWrapper (random network distillation), NGUWrapper (extends RNDWrapper with episodic novelty memory, reward += beta * bonus * episodic) — evaluated on maze/heist (benchmark #7).

1.3. Seeds and Evaluation

  • Training: 5 seeds (42,43,44,45,46) with fixed PPO seed + procgen rand_seed — mean±std across seeds recorded in statistics.json.
  • Evaluation: 10 episodes deterministic=False (stochastic, correcting spatial which gave 0.00 deterministic vs 4.00 stochastic in 8k tests) on eval 0.
  • Monitoring: tensorboard --logdir logs_* (events.out.tfevents.*).

1.4. Why 100k Steps? Scope of the Low-Data Regime

Standard Procgen literature (original paper, IDAAC/PPG) reports 5M–25M steps for near-human performance — and data from this study corroborates this: at 100k, absolute scores remain low (starpilot ~2.6, bossfight ~0.4, heist ~0.7; only coinrun saturates early, reaching 8.0 at 50k). These two statements do not conflict — they address distinct questions:

Regime Research Question Typical Budget
Solving the game "Can high absolute asymptotic performance be attained?" 5M–25M
Comparing inductive biases (this study) "Which architecture extracts the most learning per step under a constrained budget?" 100k

100k is not a defect — it is the formal definition of the experimental regime (low-data regime, as in sample-efficiency and data-augmentation literature): architectural inductive biases manifest early (see the ⚡ AUC axis in section 3.9 and the ICM trajectory in section 3.10), while computational cost remains tractable (~10 min/model on an RTX 4070 Laptop; a grid of 115+ models would be prohibitive across millions of steps).

Honest limitation: ranking order at 100k may not persist under larger budgets (a slower architecture with a higher asymptotic ceiling loses here but might win at 25M). Therefore, conclusions strictly hold for this budget, and roadmap item #6 in section 6.1 (scaling 100k→250k→500k on winners) was designed specifically to test advantage persistence.


2. Executed Benchmarks

# Script Game(s) Timesteps Seeds Configs Time Log
1 compare_procgen.py coinrun 50k 5 classic/cbam/spatial/mlp_vector ~35 min 5×50k logs_procgen/comparison_coinrun_20260827_204023
2 compare_world_models.py bossfight 100k 5 vae/ae/recon/contrastive ~67 min 5×100k logs_world_models/comparison_bossfight_20260827_193327
3 compare_suite.py bossfight+starpilot+dodgeball 100k 5 4 WM + 4 CNN + 3 Augment = 11 per game ~8h 16.5M steps 20:41→04:47 logs_suite/suite_bossfight_starpilot_dodgeball_20260827_204109
4 compare_bossfight_hard.py bossfight hard 100k 5 11 ~2.5h 07:21→09:57 logs_bossfight_hard/comparison_bossfight_hard_20260828_072148
5 compare_augment_contrastive.py bossfight 100k 5 crop/color/noise ~80 min (embedded in suite) logs_suite *_aug_*
6 compare_new_archs.py bossfight+starpilot+dodgeball 100k 5 impala/impoola/lstm_attention/vit/resnet18 ~12h 13:45→01:39 7.5M logs_new_archs/new_archs_bossfight_starpilot_dodgeball_20260828_134545
7 compare_maze_heist.py maze+heist 100k 5 ppo/icm/rnd/ngu ~6.5h 01:48→08:23 4M logs_maze_heist/maze_heist_maze_heist_20260829_014802
8 compare_combined.py suite+new_archs aggregation — — global ranking of 16 architectures immediate (no training) results/global_16.png
9 re_eval_scorecard.py re-eval of new_archs+maze_heist — — 115 zips × 30 eps stoch+det + gap ~70 min (no training) results/re_eval_results.json
10 compare_suite_retrain.py bossfight+starpilot+dodgeball 100k 5 suite retrain (11 configs, new protocol) ~28h 30/08→31/08 16.5M logs_suite_retrain/suite_retrain_zips + results/retrain_results.json
11 re_eval_100.py definitive re-eval of 275 zips — — 100 eps stoch+det + gap (no retrain) ~5h 31/08 results/eval100_results.json
12 compare_hrl.py (independent) jumper+plunder 100k frames 5 flat vs skip4 vs hrl (section 11) ~6-12h 31/08 logs_hrl/hrl_zips + results/hrl_results.json
13 compare_hrl_learned.py (independent) jumper+plunder 100k frames 5 hrl_learned arm (latent skills, section 11) ~4-8h, sequential to #12 idem (*_hrl_learned_*, .pt)
14 compare_algo_families.py (independent) starpilot+dodgeball+bossfight 100k 5 ppo/a2c (policy) vs dqn/qrdqn (value), section 12 ~9h 01/09→02/09 logs_algo/algo_zips + results/algo_families_results.json

3. Official Results (5 Seeds, 10 Eps Eval)

3.1. Coinrun 50k — CNN vs MLP (Same Game With/Without CV)

logs_procgen/comparison_coinrun_20260827_204023/statistics.json:1

Config Mean Std Min Max
classic_pixels 7.6 1.2 6.0 9.0
attention_cbam_pixels 8.0 0.0 8.0 8.0
attention_spatial_pixels 6.2 3.31 0.0 9.0
mlp_vector (16×16 256D without CV) 8.0 0.89 7.0 9.0

Analysis: Stable CBAM 8.0 outperforms classic 7.6; MLP 8.0 matches the best CNN — coinrun easy 200 is purely reactive and does not require complex spatial vision (downsample 256D suffices). Pure Spatial attention proved unstable (0.0 on 1 seed) without a residual connection.

3.2. Bossfight 100k — World Models

logs_world_models/comparison_bossfight_20260827_193327/statistics.json:1

Config Mean Std
vae 0.16 0.16
ae 0.30 0.50
recon 0.02 0.04
contrastive 0.36 0.62

contrastive performs best, but all remain <0.5 — bossfight 100k is insufficient when trained standalone.

⚠️ Methodological & Architectural Clarification (Gradient Flow & Auxiliary Pretext Objectives): In the root PyTorch / SB3 benchmark (compare_world_models.py), the modules in models/world_model_extractors.py served as custom BaseFeaturesExtractor architectures within standard Stable-Baselines3 PPO. During model.learn(), SB3 backpropagates gradients exclusively from the PPO policy and value heads through forward(). Because no custom policy was attached to compute auxiliary reconstruction loss (MSE) or KL divergence during the backward pass, the decoders (fc_dec, deconv1-3, and dream()) never received gradients and remained at random initialization. Consequently, the scores above reflect the inductive bias of an information/sampling bottleneck in the visual encoder rather than learned generative world modeling. Fully end-to-end recurrent dynamics modeling, latent imagination rollouts, and active reconstruction gradients are implemented in jax_port/train_dreamer.py.

3.3. Suite 100k — 3 Games (11 Configs/Game)

logs_suite/suite_bossfight_starpilot_dodgeball_20260827_204109/suite_statistics.json:1

Game Best Mean 2nd Worst
bossfight (~0.5) spatial 0.76±0.90 cbam 0.58, classic 0.54 contrastive 0.36 recon 0.02
starpilot (~2.0) spatial 2.6±0.82 aug_crop 2.12±0.79, cbam 2.1, ae 2.08 color 1.94, noise 1.61 —
dodgeball (~1.2) classic 1.48±0.69 cbam 1.31, spatial 1.28 vae 1.2 recon 0.88

Augment: crop outperforms color/noise in starpilot (+31%) and dodgeball; color takes second in bossfight. Overall: classic wins dodgeball, spatial wins starpilot — rankings invert across games; a single game introduces significant inductive bias (the Procgen standard is 16 games; 3 is the bare minimum).

3.4. Bossfight HARD 100k — Stress Test

logs_bossfight_hard/comparison_bossfight_hard_20260828_072148/statistics.json:1

Config Mean Std
vae 0.43±0.54 ae 0.38, aug_crop 0.32, mlp 0.26
cnn 0.02±0.04 (classic/cbam/spatial collapse to zero)

hard compresses all returns to 0.0-0.4 (compared to 0.5-0.76 in easy); CNN collapses in 100k — hard would require 200k (~6h) and does not warrant a full suite hard; easy constitutes a fairer benchmark.

3.5. New Archs 100k — 5 New Architectures × 3 Games

logs_new_archs/new_archs_bossfight_starpilot_dodgeball_20260828_134545/statistics.json:1

Game Best Mean 2nd Worst
bossfight lstm 0.36±0.57 vit 0.30, impala 0.28 resnet 0.02 impoola 0.06
starpilot lstm 2.44±0.56 resnet 2.28, impoola 2.2 vit 2.1, impala 1.78 —
dodgeball resnet 1.72±0.65 vit 1.2, impoola 1.12 impala 1.08 lstm 0.80

ViT/ResNet fail to surpass spatial 2.6 in starpilot or classic 1.48 in dodgeball; lstm wins bossfight/starpilot but underperforms in dodgeball.

3.6. Maze+Heist 100k — PPO vs ICM vs RND vs NGU (Exploration)

results/exploration_remeasure.json (re-measured 24/09/2026, 40 models across seeds 42-46)

Game ppo icm rnd ngu
maze (10 eps) 1.80±0.84 2.20±1.92 2.40±2.61 2.20±2.28
heist (10 eps) 0.80±0.45 0.80±0.84 1.60±1.14 1.20±1.30
maze (100 eps) 2.58 2.36 2.54 2.76
heist (100 eps) 0.72 0.60 1.04 0.80

✅ Re-measured Arms (24/09/2026): The previous rnd and ngu arms ran with broken linear projections (nn.Linear(1024, 512) instead of 2304) that raised exceptions silently swallowed by except Exception: pass, producing identical clones of vanilla PPO. The re-measurement with verified dynamic stem sizing in models/bonuses.py recorded active intrinsic reward computation on 100% of steps (100,096/100,096 steps across all runs). Scientific Finding on Bonus Scaling: In SB3, the raw feature MSE produces an intrinsic reward of $\sim 10^{-5}$, which under default $\beta=0.01$ amounts to an effective bonus of $\sim 1.05 \times 10^{-7}$ per step against the $0/1$ sparse extrinsic reward. Consequently, while rnd achieves a modest advantage on heist (1.04 vs 0.72), exploration bonuses without running variance normalization (Burda et al., 2018) fail to significantly break away from PPO. Note that the uniform random baseline scores 4.40±5.01 on maze and 2.80±4.54 on heist under 50 episodes (section 18.1), confirming that 100k steps is insufficient for any policy to master deep maze navigation or 3-key heist puzzles. Follow-up (27/09/2026): this null is a statement about scale, and it is reversible — section 3.6.1 re-trains the same three arms with the bonus rescaled to $O(\beta)$ and every arm then beats this control on stochastic return.

3.6.1. Scaled-Bonus Arms (27/09/2026) — the Null Was the Scale, and It Reverses

results/exploration_normalized.json — 30 new cells (2 games × 3 bonus arms × 5 seeds × 100k), trained with compare_maze_heist.py --normalize and summarized by summarize_exploration_normalized.py.

What changed, precisely. The published arms add beta × raw prediction MSE to every step, which measures $\sim 10^{-7}$. The scaled arms add beta × (MSE / trailing-window mean of that MSE) — divisive reward normalization, chosen because it stays positive, monotone in novelty, and $O(1)$ whatever level the predictor converges to. Two other forms were measured and rejected: dividing by the running std alone still injected 5.3e-5 (ngu) and 1.1e-4 (icm) per step on a live smoke (the error is heavy-tailed, so the std is set by rare spikes and the typical ratio stays far below 1), and the textbook demeaned z-score floored at 0 is degenerate for a bonus that decays by design — as the predictor learns, every new error sits below its own running mean, so the injected bonus clipped to 0.0 on all 400 steps of a probe, for all three arms. beta=0.01, the optimizer, the env protocol and the seeds are untouched, so the scale is the only variable. ppo has no bonus to rescale: its rows are reused from the frozen 24/09 grid and the reuse is declared in _provenance.control (both protocols; the 100-ep row carries reused_from).

Measured injected magnitude, per cell and stable across seeds to ±2%: icm 9.49–9.52e-3, rnd 5.92e-3 (maze) / 7.84e-3 (heist), ngu 2.49e-3 / 3.76e-3 per step — four to five orders of magnitude above the published arms — with the per-step max pinned at beta × clip = 5.0e-2, i.e. the clip binds and is part of the arm, not an accident.

100 stochastic episodes on unseen levels, $n=5$, Student-t 95% CI, Welch $p$ and Hedges $g$ against the reused control:

Arm maze mean 95% CI $p$ $g$ heist mean 95% CI $p$ $g$
ppo (control, reused) 2.58 [1.49, 3.67] — — 0.72 [0.29, 1.15] — —
icm 4.18 [3.75, 4.61] 0.0118 2.17 3.04 [2.21, 3.87] 0.00046 3.94
rnd 5.04 [4.19, 5.89] 0.0014 2.83 3.52 [2.32, 4.72] 0.0017 3.49
ngu 4.18 [3.16, 5.20] 0.0179 1.70 2.84 [1.79, 3.89] 0.0029 2.97

Reading. Curiosity is no longer inert: every scaled arm beats the control on both games, with large effect sizes ($g$ 1.70–3.94). Corrected for the six simultaneous comparisons (Bonferroni, $\alpha=0.05$), all three heist arms survive ($p_{adj}$ 0.0028–0.0174) and on maze only rnd does ($p_{adj}=0.0081$; icm 0.071, ngu 0.107, and ngu's interval overlaps the control's). At the 10-episode protocol the ordering is the same (maze 3.00/4.00/2.80 vs ppo 1.80; heist 4.40/3.80/4.40 vs 0.80). Caveat that matters more than the win. The gain is exploration-dependent, not competence. Under deterministic execution the same checkpoints return maze 0.00 (icm) / 0.70 (rnd) / 0.46 (ngu) and heist 0.14 / 0.26 / 0.20 — the same near-zero band the unnormalized arms and the control occupied (control: 0.22 / 0.08). gen_gap (train-level minus unseen-level) is positive for every arm (+0.35 to +1.42). So a live curiosity bonus at $\beta=0.01$ buys stochastic return by keeping the policy novelty-seeking; it does not teach it a solve, and an argmax readout of the resulting weights loses.

3.7. Global 16 Architectures — 3-Game Average (Top 10 of 16 Displayed)

logs_suite + logs_new_archs aggregated (16.5M + 7.5M steps) — mean of 3 means per architecture:

Rank Architecture Global Mean Per-Game Breakdown (B/S/D)
1 spatial 1.54 0.76 / 2.60 / 1.28
2 resnet18 1.34 0.02 / 2.28 / 1.72
3 classic 1.33 0.54 / 1.98 / 1.48
4 cbam 1.33 0.58 / 2.10 / 1.31
5 mlp_vector 1.25 0.58 / 2.07 / 1.11
6 lstm_attention 1.20 0.36 / 2.44 / 0.80
7 vit 1.20 0.30 / 2.10 / 1.20
8 aug_crop 1.16 0.54 / 2.12 / 0.84
9 impoola 1.12 0.06 / 2.20 / 1.12
10 ae 1.11 0.30 / 2.08 / 0.96

The Top 3 are pure CNN variants (spatial/resnet/classic); World Models (vae 0.88, recon 0.90) and contrastive 0.97 rank below MLP 1.25 in suite 100k easy — CV with attention still outperforms World Models in Procgen at 100k.

3.8. Statistical Robustness — 95% CI + Effect Size (scorecard_analysis.py, results/scorecard.json)

With n=5 seeds, 95% CI is computed via Student's t (df=4, critical value 2.776); Cohen's d compares top-1 vs top-2 per game:

Data provenance: the per-seed returns for the runs whose raw logs were pruned (coinrun 50k, bossfight 100k world models) are now read from results/legacy_records.json, the single machine-readable record of those runs — they used to be literals pasted inside scorecard_analysis.py. tests/test_legacy_records.py asserts that those arrays still reproduce the mean/std/ci95 published below, and the same file supplies the section 3.7 ranking used by retrain_analysis.py.

Game Top-1 vs Top-2 Cohen's d CIs Overlap? Conclusion
bossfight (WM+New) lstm 0.36 vs contrastive 0.36 0.0 ✅ Yes Statistical tie
starpilot lstm 2.44 [1.66, 3.22] vs resnet 2.28 [1.37, 3.19] 0.235 (small) ✅ Yes Not statistically significant
dodgeball resnet 1.72 [0.81, 2.63] vs vit 1.2 [0.90, 1.50] 0.956 (large) ✅ Yes Large effect size, but n=5 underpowered
maze ppo 2.4 [0.32, 4.48] vs icm 1.8 [-0.24, 3.84] 0.36 (small) ✅ Yes ICM "worse" not confirmed
heist ppo 0.8 vs icm 0.6 0.23 (small) ✅ Yes Statistical tie
coinrun cbam 8.0 [8.0, 8.0] vs mlp 8.0 [6.76, 9.24] 0.0 — Tie; cbam exhibits zero variance

Critical takeaway: No top-1 vs top-2 difference is statistically significant with 5 seeds — the rankings in sections 3.x represent empirical trends, not definitive conclusions. Only dodgeball resnet vs vit (d=0.956) approaches a reliable effect. The exact ties between RND/NGU and PPO (d=0, identical per-seed returns) were not a null result — they were the broken wrapper of section 3.6, confirmed on 23/09/2026. ⚠️ Superseded: This ranking was measured under the legacy protocol (10 eps); retraining the suite under the new protocol (section 3.11) inverts the top-2 (mlp_vector surpasses spatial).

3.9. Sample Efficiency (AUC) — Partial Scorecard (rollout/ep_rew_mean TensorBoard, 5 Seeds)

AUC_norm = ∫reward·dsteps / 100k (mean of 5 seeds) — computed exclusively for new_archs/maze_heist (legacy TensorBoard logs pruned):

Game Best AUC Ranking by AUC vs Final Evaluation Ranking
bossfight lstm 0.247 lstm > impoola 0.186 > impala 0.141 > resnet 0.107 > vit 0.064 ≈ Identical to final ranking
starpilot impala 2.336 impala > resnet 2.325 > vit 2.297 > lstm 2.284 > impoola 2.23 Inverted: impala has best AUC but worst final return (1.78) — learns fast, plateaus early
dodgeball resnet 1.175 resnet > impala 1.169 > impoola 1.151 > lstm 1.114 > vit 1.032 ≈ Identical to final ranking
maze ppo=rnd=ngu 3.785 all > icm 3.658 ICM trails in AUC as well (not only at convergence)
heist ppo=rnd=ngu 1.826 all > icm 1.792 Identical pattern

rnd/ngu rows in this table are the disabled-wrapper arms of section 3.6, not curiosity results. Finding: starpilot_impala displays the highest learning rate curve but lowest final evaluation reward — proving that "fastest learner" ≠ "best asymptotic model" (the ⚡ AUC axis is orthogonal to the 🧠 performance axis).

3.10. Extended Re-Evaluation — re_eval_scorecard.py (Completed: 115/115, 0 errors)

115 models (new_archs 75 + maze_heist 40) re-evaluated across 30 eps unseen (stoch + det, levels seed+1000) and 15 eps training levels (generalization gap). Full data in results/re_eval_results.json:

❓ Why do absolute values differ from sections 3.4/3.6 when weights are identical? Two independent causes: (a) Different level distribution — the new protocol samples unseen levels via seed+1000 (original used an alternative seed offset), shifting sampled level difficulty; (b) 30 vs 10 episodes — 10-episode averages had high sample variance. Hence, only relative ranking within the same evaluation set is scientifically valid ("vs 3.4/3.6" column), never absolute comparisons across protocols.

Config stoch unseen det unseen gen gap vs 3.4/3.6 (10 eps)
bossfight_resnet18 0.39 0.03 −0.12 4th → 1st
bossfight_impala 0.33 0.43 −0.10 Improves
bossfight_lstm_attention 0.32 0.36 −0.20 1st → 3rd
bossfight_vit 0.29 0.16 −0.27 Stable
bossfight_impoola 0.25 0.43 −0.21 Drops to last
starpilot_lstm_attention 2.63 1.25 +0.15 Retains top-1
starpilot_resnet18 2.53 0.91 −0.51 Stable
starpilot_impoola 2.44 1.29 −0.19 Stable
starpilot_impala 2.00 1.29 +0.31 5th → 4th
starpilot_vit 1.88 0.41 −0.07 Drops to last
dodgeball_resnet18 1.07 0.89 +0.27 Retains top-1
dodgeball_impoola 1.05 0.43 −0.07 Improves
dodgeball_impala 1.04 0.44 −0.11 Improves
dodgeball_lstm_attention 0.99 0.76 +0.24 5th → 4th
dodgeball_vit 0.91 0.40 −0.21 2nd → 5th
maze_icm 2.80 0.47 +0.80 3rd → 1st
maze_ppo=rnd=ngu 2.47 0.60 +0.73 Top-1 → tied 2nd
heist_icm 0.73 0.33 −0.47 2nd → 1st
heist_ppo=rnd=ngu 0.67 0.07 0.00 Top-1 → tied 2nd

3 Findings: (1) ICM leads in maze/heist — inverting section 3.6; at 30 eps, intrinsic curiosity bonuses become visible. (2) vit fails to hold 2nd place in dodgeball (1.20 → 0.91) — confirming section 3.8 (n=5 underpowered). (3) Deterministic evaluation collapses exploration policies (maze 2.47 stoch → 0.60 det), while gen gap is ≈ 0/negative across most models — models do not overfit/memorize the 200 training levels; they simply remain sample-constrained (exceptions: maze/heist/dodgeball resnet, gap +0.7~+0.8).

3.11. Retraining the Suite under New Protocol — compare_suite_retrain.py (Completed: 160/165; 5 NaN in wm_vae Persisted Across 2 Retry Rounds)

The 165 models of the original suite (4 WM + 4 CNN + 3 augment × 3 games × 5 seeds, hyperparameters identical to compare_suite.py:26) were retrained from scratch with the new protocol (30 eps stoch+det + gap, weights stored in logs_suite_retrain/suite_retrain_zips). Data in results/retrain_results.json, analysis in results/retrain_analysis.json (retrain_analysis.py):

❓ Why do figures differ from the original suite (sections 3.3/3.7)? Three independent factors: (a) unseen level evaluation seed offset (seed+1000), (b) 30 vs 10 episodes, and (c) new model weights — newly instantiated training runs subject to CUDA/cuDNN non-determinism (seen in the 5 divergent seeds of wm_vae). Disentanglement example: in starpilot, spatial scored 2.63 ≈ legacy 2.60 (difference ~0 → factors (a)+(b)+(c) negligible here), whereas in bossfight it dropped from 0.76 → 0.36 — since the protocol is identical across all configs, changes in relative ranking reflect genuine evaluation fidelity rather than random artifacts.

Top 5 per game (stochastic unseen, 30 eps):

Game 1st 2nd 3rd 4th 5th
bossfight aug_crop 0.68 contrastive 0.48 = aug_noise 0.48 cbam 0.40 mlp 0.37 spatial 0.36
starpilot spatial 2.63 mlp 2.49 classic 2.20 = aug_crop 2.20 wm_ae 2.17 cbam 2.16
dodgeball mlp 1.21 cbam 1.15 classic 1.07 = spatial 1.07 = aug_color 1.07 wm_ae 1.05 aug_crop 1.04

New Global Ranking (mean across 3 games, identical aggregation rule to section 3.7) vs Legacy:

Rank Architecture New Legacy (3.7) Δ
1 mlp_vector 1.36 1.25 (5th) +0.11, 5th→1st
2 spatial 1.35 1.54 (1st) −0.19, 1st→2nd
3 aug_crop 1.31 1.16 +0.15
4 cbam 1.24 1.33 (4th) −0.09
5 classic 1.16 1.33 (3rd) −0.17, 3rd→5th
6 wm_ae 1.13 1.11 +0.02
7 wm_recon 1.05 ~0.90 Improves
8 aug_noise 1.04 — —
8 wm_contrastive 1.04 ~0.97 Improves
10 aug_color 1.02 — —
11 wm_vae 0.92 — n=2–3 (NaN)

4 Findings: (1) mlp_vector dethrones spatial — the shift stems from bossfight (spatial 0.76→0.36), whereas spatial holds in starpilot (2.63 ≈ legacy 2.60) and dodgeball. (2) World Models are not universally deficient: weakness was concentrated in bossfight; in starpilot (wm_ae 2.17, recon 1.97) and dodgeball (wm_ae 1.05) they tie with standard CNNs. (3) aug_crop is the superior data augmentation (bossfight 0.68, game winner). (4) dodgeball yields poor architectural separation (spread 1.21→0.88 vs 0.68→0.12 in bossfight) — low resolution benchmark. Combining re-evaluated new_archs/maze_heist, the unified global ranking is: mlp_vector 1.36 > spatial 1.35 > lstm_attention 1.34 > aug_crop 1.31 (all statistically indistinguishable per section 3.8). Total: 275 models evaluated under the new protocol. ⚠️ Update: The figures above reflect 30 eps; definitive 100 eps evaluation (section 3.12) shifts the top-1 in starpilot/dodgeball and dissolves ICM's advantage.

3.12. Definitive Protocol — 100 eps Across All 275 Models (re_eval_100.py, Completed: 275/275)

Re-evaluation of identical checkpoints across 100 eps unseen stochastic + 100 eps deterministic + 15 eps training (no retraining). Data in results/eval100_results.json, comparative analysis 30 vs 100 in results/eval100_analysis.json (eval100_analysis.py):

Top Configurations per Game @ 100 eps:

Game 1st 2nd 3rd vs @ 30 eps
bossfight aug_crop 0.68 aug_noise 0.54 = contrastive 0.54 mlp 0.53 Top-1 stable
starpilot mlp 2.67 spatial 2.45 resnet18 2.44 lstm 1st → 4th (2.43)
dodgeball resnet18 1.16 cbam 1.08 = mlp 1.08 wm_recon 1.02 mlp 1st → 3rd; recon 12th → 4th
maze ngu 2.76 ppo 2.58 rnd 2.54 icm 2.36
heist rnd 1.04 ngu 0.80 ppo 0.72 icm 0.60

Global Suite Ranking @ 100 eps: mlp_vector 1.43 > resnet18 1.34 > spatial 1.28 > lstm_attention 1.26 > aug_crop 1.25.

4 Findings from the 30→100 Upgrade: (1) mlp_vector consolidates global 1st place (1.25@10eps → 1.36@30 → 1.43@100 — the only model consistently at the top across all protocols). (2) Exploration arms re-measured (24/09/2026): In maze, ngu achieves 2.76, ppo 2.58, rnd 2.54, and icm 2.36; in heist, rnd achieves 1.04, ngu 0.80, ppo 0.72, and icm 0.60. All differences remain within overlapping 95% confidence intervals, confirming that while wrappers execute correctly, unnormalized intrinsic error ($10^{-7}$/step) is insufficient to fundamentally reshape policy exploration in Procgen. (3) Top-1 in starpilot/dodgeball inverts again (lstm→mlp, mlp→resnet18) while bossfight remains stable — the upper tier is solid, while mid-tier rankings remain fluid. (4) Mean absolute delta |Δ| is 0.108 between @30 and @100: variance converges, and residual position shifts reinforce section 3.8: with 5 seeds, no strict ranking order is mathematically frozen; the study reports credible trends with confidence intervals.

3.13. Budget Scaling — compare_budget_scaling.py (Completed: 24/24, 0 errors)

Roadmap item #6: Does the advantage of top models persist under larger budgets? Scope: resnet18 + mlp_vector × starpilot + dodgeball × 3 seeds (42-44) × 250k/500k; the 100k baseline is established (definitive evaluation, identical seeds). Data in results/budget_results.json, analysis in results/budget_analysis.json (budget_analysis.py):

Curve (stochastic unseen) 100k 250k 500k Verdict
starpilot_resnet18 2.30±0.58 2.73±0.27 2.52±0.49 Stagnant
starpilot_mlp_vector 2.67±0.23 3.23±0.55 2.64±0.41 Stagnant
dodgeball_resnet18 1.07±0.38 1.01±0.15 1.00±0.07 Stagnant
dodgeball_mlp_vector 1.17±0.35 0.91±0.15 0.91±0.15 Stagnant

3 Findings: (1) No learning curve scales monotonically with budget — 250k represents a minor inflection within empirical noise, and 500k regresses or ties; a 5× budget neither created nor eliminated an advantage. Answer to item #6: larger budgets were not required to differentiate these architectures; 100k proved sufficient. (2) Per-game relative advantages persist: mlp_vector leads starpilot at 250k (3.23 vs 2.73) and resnet18 leads dodgeball across both budgets (1.01/1.00 vs 0.91) — confirming section 3.12. (3) Generalization gap remains ≈ 0/negative even at 500k — overfitting/memorization does not emerge with extended budgets, reinforcing section 3.10.

3.14. Robust Statistical Evaluation — NeurIPS 2021 RLiable Metrics (rliable_metrics.py, run_rliable_eval.py)

To resolve the statistical limitations of raw point estimates (sample means and standard deviations) across heterogeneous Procgen environments, we implemented the evaluation methodology proposed by Agarwal et al. (NeurIPS 2021) ("Deep Reinforcement Learning at the Edge of the Statistical Precipice").

Using the definitive 100-episode evaluation dataset across all 275 models (results/eval100_results.json), scores are Min-Max normalized per game using empirical extremal bounds:

  • bossfight: $[0.00, 1.51]$
  • dodgeball: $[0.24, 1.78]$
  • heist: $[0.10, 1.40]$
  • maze: $[0.80, 3.80]$
  • starpilot: $[0.28, 3.10]$

For each architecture evaluated across the 3 core suite games (bossfight, starpilot, dodgeball), we computed the Interquartile Mean (IQM), 5% Trimmed Mean, and 95% Stratified Bootstrap Confidence Intervals (with $B = 10{,}000$ resamples), along with Performance Profiles ($\tau \in [0, 1]$) and Pairwise Probability of Improvement:

Aggregation correction (23/09/2026): the aggregator previously applied the metric once to the pooled array of all games' scores, which lets any game with more seeds dominate the estimate. It now follows Agarwal et al. literally — the metric is computed within each game and then averaged across games (equal weight per game), with resampling stratified inside each game. Because the three suite games have equal seed counts, the point estimates shift only slightly, but the confidence intervals widen (they are no longer computed on a 45-element pooled sample), and the IQM ordering of resnet18 and cnn_mlp_vector inverts. The single bootstrap constant is now rliable_metrics.NUM_BOOTSTRAPS = 10000; the forest and profile panels previously ran at $B = 2{,}000$.

Architecture Normalized IQM 95% Bootstrap CI 5% Trimmed Mean 95% Trimmed CI
resnet18 0.532 [0.368, 0.736] 0.546 [0.410, 0.687]
cnn_mlp_vector 0.529 [0.420, 0.733] 0.581 [0.462, 0.710]
cnn_cbam 0.520 [0.363, 0.646] 0.502 [0.394, 0.607]
aug_crop 0.494 [0.374, 0.654] 0.513 [0.398, 0.632]
cnn_spatial 0.488 [0.383, 0.632] 0.505 [0.411, 0.605]
lstm_attention 0.483 [0.385, 0.635] 0.499 [0.406, 0.602]
wm_vae 0.440 [0.322, 0.574] 0.443 [0.324, 0.572]
aug_noise 0.434 [0.253, 0.543] 0.395 [0.273, 0.508]
wm_contrastive 0.434 [0.253, 0.543] 0.395 [0.273, 0.508]
impala 0.419 [0.311, 0.536] 0.426 [0.335, 0.516]
impoola 0.413 [0.319, 0.616] 0.453 [0.345, 0.583]
cnn_classic 0.411 [0.344, 0.522] 0.432 [0.362, 0.508]
wm_recon 0.407 [0.276, 0.598] 0.438 [0.320, 0.573]
aug_color 0.399 [0.255, 0.493] 0.381 [0.283, 0.467]
wm_ae 0.374 [0.311, 0.452] 0.381 [0.332, 0.433]
vit 0.326 [0.229, 0.538] 0.372 [0.257, 0.512]

Key Robust Statistical Findings:

  1. Overlapping 95% Confidence Intervals Across Top Tier: The stratified bootstrap 95% CIs for the top 6 architectures (resnet18 $[0.368, 0.736]$, cnn_mlp_vector $[0.420, 0.733]$, cnn_cbam $[0.363, 0.646]$, aug_crop $[0.374, 0.654]$, cnn_spatial $[0.383, 0.632]$, lstm_attention $[0.385, 0.635]$) substantially overlap. Under NeurIPS 2021 statistical guidelines, ranking these models by point IQM estimate alone is statistically unwarranted. The two leading architectures also disagree across aggregators (resnet18 wins IQM, cnn_mlp_vector wins trimmed mean and unnormalized mean) — further evidence that the top tier is a single statistical cluster.
  2. Pairwise Probability of Improvement: Agarwal et al.'s probability of improvement $P(X &gt; Y)$ computes the probability that a randomly selected seed/run of architecture $X$ yields higher normalized return than architecture $Y$:
    • $P(\text{mlp_vector} &gt; \text{resnet18}) = 0.587$ (95% CI: $[0.360, 0.800]$): Because the 95% CI spans $0.50$, neither model statistically dominates the other.
    • $P(\text{mlp_vector} &gt; \text{cnn_spatial}) = 0.640$ (95% CI: $[0.427, 0.840]$): also non-significant.
    • $P(\text{mlp_vector} &gt; \text{aug_crop}) = 0.633$ (95% CI: $[0.420, 0.827]$).
    • $P(\text{resnet18} &gt; \text{cnn_cbam}) = 0.500$ (95% CI: $[0.267, 0.733]$): Exact parity. Every pairwise comparison among the top 5 falls inside $[0.36, 0.64]$ with CIs crossing $0.5$ — no architecture in this benchmark is demonstrably superior to another.
  3. Performance Profiles: The cumulative performance profile (see Figure 4.9, results/rliable_profile.png) demonstrates that cnn_mlp_vector and resnet18 dominate higher normalized return thresholds ($\tau &gt; 0.6$), whereas lstm_attention, cnn_spatial, and cnn_cbam show higher probability mass at moderate thresholds ($\tau \approx 0.5$). Data serialized in results/rliable_scorecard.json.
  4. Canonical (Agarwal) normalization covers only 2 of the 3 suite games: dodgeball is excluded because the reference architecture cnn_classic does not beat the empirical random policy there (see section 18.1). maze and heist are excluded because no cnn_classic arm was ever trained on them. A previously published classic_means value of 1.0 for those two games was a silent code fallback, not a measurement; it has been removed.

4. Figures and Videos

All generated visual artifacts and figures are versioned under results/.

Provenance (added 23/09/2026 — previously no figure had a stated producer):

Figures Drawn by Data source Rebuildable today
coinrun_50k_cnn_vs_mlp.png (4.1), bossfight_100k_world_models.png (4.2) report_figures.py results/legacy_records.json (per-seed values of the pruned runs) ✅ py -3.10 report_figures.py
rliable_profile.png (4.9) run_rliable_eval.py results/eval100_results.json + results/random_baselines.json ✅ py -3.10 run_rliable_eval.py
symbolic_regression_*.png (21.3–21.5) symbolic_regression.py jax_port/cells_summary.json → results/symbolic_regression.json ✅ py -3.10 symbolic_regression.py
suite_* (4.3), bossfight_hard_100k.png (4.4), new_archs_* (4.5), maze_heist_* (4.6), global_16.png (4.7) compare_suite.py, compare_bossfight_hard.py, compare_new_archs.py, compare_maze_heist.py, compare_combined.py their logs_*/<run>/ directories, which are git-ignored and were pruned ❌ needs the benchmark re-run (commands in section 5); the files below are the originals from those runs

py -3.10 report_figures.py --check fails if this README embeds a figure that is not in the repository, or one with no recorded producer in the table above. It runs as a step in .github/workflows/tests.yml and is asserted again in tests/test_report_figures.py, so a figure can no longer drift away from its data silently. Regenerating 4.1/4.2 in place reproduces the same means and per-seed values; the error bars are sample standard deviations (ddof=1, matching section 3.8) and therefore slightly wider than the population standard deviations quoted in section 7.

4.1. Coinrun 50k — CNN vs MLP (With/Without Vision)

Coinrun 50k — CNN vs MLP

4.2. Bossfight 100k — World Models

Bossfight 100k — World Models

4.3. Suite 100k — 3 Games × 11 Architectures

Bossfight Starpilot Dodgeball
Suite Bossfight Suite Starpilot Suite Dodgeball

4.4. Bossfight HARD 100k — Stress Test

Bossfight HARD 100k

4.5. New Archs 100k — 5 New Architectures × 3 Games

New Archs Bossfight | New Archs Starpilot | New Archs Dodgeball

4.6. Maze+Heist 100k — PPO vs ICM/RND/NGU

Maze | Heist

4.7. Global 16 — 3-Game Average

Global 16

4.8. Videos

Side-by-side behavioral videos are generated on demand (visualize_side_by_side.py) — bossfight with dreams (top=real, bottom=dream() from VAE/AE/Recon; Contrastive renders no dream) and coinrun with agents side-by-side. Requires .zip checkpoints produced during training:

py -3.10 visualize_side_by_side.py --benchmark world_models --game bossfight --log_dir ./logs_world_models --mode mp4 --out results/bossfight_dreams.mp4 --steps 600 --device cuda
py -3.10 visualize_side_by_side.py --benchmark procgen --game coinrun --log_dir ./logs_procgen --mode mp4 --out results/coinrun_side_by_side.mp4 --steps 600 --device cuda

4.9. RLiable Performance Profiles (NeurIPS 2021)

RLiable Performance Profiles


5. How to Reproduce

# Environment setup (Python 3.10 mandatory for Procgen wheels) — exact pinned versions in requirements.txt
pip install -r requirements.txt

# Or install manually with verified release pins:
py -3.10 -m pip install procgen==0.10.7 stable-baselines3==2.9.0 gymnasium==1.3.0 gym==0.26.2 torch==2.5.1+cu121 opencv-python==4.8.0.74 --extra-index-url https://download.pytorch.org/whl/cu121

# Execute 5-seed benchmarks
py -3.10 -u compare_world_models.py --timesteps 100000 --seeds 42 43 44 45 46 --num_levels 200 --log_dir ./logs_world_models --device cuda
py -3.10 -u compare_procgen.py --game coinrun --timesteps 50000 --seeds 42 43 44 45 46 --num_levels 200 --log_dir ./logs_procgen --device cuda
py -3.10 -u compare_suite.py --games bossfight starpilot dodgeball --timesteps 100000 --seeds 42 43 44 45 46 --log_dir ./logs_suite --device cuda
py -3.10 -u compare_bossfight_hard.py --timesteps 100000 --seeds 42 43 44 45 46 --log_dir ./logs_bossfight_hard --device cuda
py -3.10 -u compare_new_archs.py --timesteps 100000 --seeds 42 43 44 45 46 --games bossfight starpilot dodgeball --log_dir ./logs_new_archs --device cuda
py -3.10 -u compare_maze_heist.py --timesteps 100000 --seeds 42 43 44 45 46 --games maze heist --log_dir ./logs_maze_heist --device cuda
py -3.10 -u compare_combined.py  # Aggregates logs_suite + logs_new_archs into global ranking
py -3.10 -u re_eval_scorecard.py --device cuda  # Re-evaluates 30 eps stoch+det on 115 zips (no retraining)
py -3.10 -u compare_suite_retrain.py --device cuda  # Retrains suite under new protocol (resume-safe)
py -3.10 scorecard_analysis.py  # 95% CI + Cohen's d + AUC -> results/scorecard.json. Requires the logs_new_archs/ and logs_maze_heist/ run directories; without them it exits with an error rather than overwriting the published table with a reduced one (--logs_root / --force to override)
py -3.10 retrain_analysis.py  # Retraining analysis + updated global ranking -> results/retrain_analysis.json
py -3.10 -u re_eval_100.py --device cuda  # Definitive protocol: 100 eps across all 275 zips
py -3.10 eval100_analysis.py  # Comparative analysis 30 vs 100 -> results/eval100_analysis.json
py -3.10 random_baselines.py --episodes 50  # Empirical random anchors on unseen levels -> results/random_baselines.json (section 18.1)
py -3.10 run_rliable_eval.py  # IQM / trimmed mean / stratified bootstrap CIs / profiles -> results/rliable_scorecard.json + rliable_profile.png (section 3.14)
py -3.10 symbolic_regression.py  # system identification over the 825 committed port cells -> results/symbolic_regression.json + 3 figures (section 21)
py -3.10 report_figures.py  # Rebuilds figures 4.1/4.2 from results/legacy_records.json; prints which of the other 11 need a re-run
py -3.10 -m pytest tests -q  # 84 tests: extractors, SB3 integration, bonuses, world models, aggregation, provenance
py -3.10 probe_actions.py  # Action space probing (basis for HRL skills)
py -3.10 -u compare_hrl.py --device cuda  # Independent benchmark: HRL vs Flat RL (jumper/plunder)
py -3.10 -u compare_hrl_learned.py --device cuda  # hrl_learned arm (RUN AFTER compare_hrl.py: both write to results/hrl_results.json)
py -3.10 hrl_analysis.py  # Analysis of 4 HRL arms -> results/hrl_analysis.json
py -3.10 -u compare_budget_scaling.py --device cuda  # Budget scaling 250k/500k (resume-safe)
py -3.10 budget_analysis.py  # Curves 100k->250k->500k -> results/budget_analysis.json
py -3.10 -u compare_algo_families.py --device cuda  # Value vs policy-based (section 12; requires sb3-contrib)
py -3.10 algo_analysis.py  # Analysis per family/algorithm -> results/algo_families_analysis.json
py -3.10 -u lr_sensitivity.py --device cuda  # Learning rate sensitivity test for value-based algorithms (section 12.2)

cuda Runtime Estimates: coinrun 50k 5×50k ~35 min; bossfight 100k 5×100k ~67 min; suite 100k 3 games × 11 × 5 × 100k 16.5M steps ~15h (20:41→04:47); bossfight hard ~2.5h; new archs 100k 3 games × 5 × 5 × 100k 7.5M steps ~12h (13:45→01:39); maze+heist 100k 2 games × 4 × 5 × 100k 4M steps ~6.5h (01:48→08:23); combined immediate; re-eval 115 zips ~70 min; suite retrain 165 models ~28h (bossfight ~10 min/model, dodgeball ~3 min/model); re-eval 100 eps 275 zips ~5h.

Continuous integration: .github/workflows/tests.yml runs on every push to main and on pull requests — one job installs the CPU build of the study stack and runs, in order, a compileall syntax gate over every module in the repository, python report_figures.py --check (every figure this README embeds must exist and must have a producer recorded in the section 4 table), and python -m pytest tests -q; a second job installs JAX/Flax/Optax on CPU and runs python -m jax_port.tests.run_tests, whose cases report SKIP when an environment-only dependency (procgen, jaxmarl) is absent but fail the build on any assertion error. Neither job retrains anything: they protect the analysis, extractor and documentation-integrity code, not the benchmark numbers.


6. Offline RL Benchmark (Completed 25/09/2026)

Note: Paths such as D:\mario-ds, D:\mujoco-walker, D:\Imitation-player, and D:\mario64ds-rl refer to external reference codebases outside this repository — they are not required to reproduce the benchmarks above.

# Origin Benchmark in Procgen RL Status Artifacts / Implementation
1 mujoco-walker:50 Offline RL 100k bossfight BC vs IQL vs CQL vs Decision Transformer ✅ Completed (25/09/2026) compare_offline_rl.py $\to$ results/offline_rl_results.json

6.1. Offline RL Empirical Results on Bossfight (results/offline_rl_results.json)

Evaluated four canonical offline RL paradigms on an offline dataset of 100,000 transitions (1,804 episodes) collected on bossfight under mixed exploratory and tactical behavior. All models share the canonical NatureCNN visual feature encoder backbone. Evaluation was conducted across 20 stochastic unseen episodes (num_levels=0, evaluation seed 1042), with results persisted in results/offline_rl_results.json:

Method Paradigm Mean Return ± Std 95% Student's $t$ CI Empirical Behavior
DT (Decision Transformer) Sequence Modeling ($R_t$-conditioned) 0.60 ± 2.46 [-0.55, 1.75] Conditioned on high RTG; damages boss
BC (Behavioral Cloning) Supervised Policy Learning 0.00 ± 0.00 [0.00, 0.00] Fails to avoid boss projectiles
IQL (Implicit Q-Learning) In-Sample Expectile Value + AWR 0.00 ± 0.00 [0.00, 0.00] Value collapse on pixel actions
CQL (Conservative Q-Learning) Conservative Lower-Bound Q-Penalty 0.00 ± 0.00 [0.00, 0.00] Over-conservative under sparse rewards

Scientific Insight: In pixel-observation discrete control with sparse rewards and high adversary lethality (bossfight), autoregressive sequence modeling conditioned on desired future returns (Decision Transformer) successfully discovered positive reward trajectories ($0.60$ mean return), outperforming all three value-based and behavioral cloning baselines which collapsed to $0.00$ due to distributional shift and compounding error under unguided rollouts.

6.2. Instrumentation Roadmap — 7 Prioritized Enhancements

The primary leverage point was not accumulating more architectures, but rigorously instrumenting the existing set. Ranked by cost × value:

# Enhancement Status Details
1 50–100 evaluation episodes ✅ Completed (re_eval_scorecard.py: 115/115 models, 30 eps) n_eval_episodes=10 was noisy — rankings shifted (section 3.10); legacy models from early benchmarks were purged, so only surviving 115 checkpoints were re-evaluated
2 Dual eval: deterministic=True + False ✅ Completed (same script, stoch/det columns in section 3.10) Verified notice in section 1.3: deterministic mode collapses stochastic exploration policies (maze 2.47→0.60) — both must be reported
3 Confidence intervals + effect sizes ✅ Completed (scorecard_analysis.py → section 3.8) With n=5 seeds, marginal differences do not establish superiority; Student's t 95% CI and pairwise Cohen's d in results/scorecard.json
4 Scorecard: robustness + sample efficiency (AUC) ✅ AUC completed (section 3.9); robustness = std Normalized AUC(reward, env_steps) from TensorBoard curves for new_archs/maze_heist; shifts focus from "who won" to "who learns faster"; did not require retraining
5 Scorecard: generalization gap ✅ Completed (item 1, no retraining) gap = train(200 levels, training seed) − unseen; ≈ 0/negative across most models → no empirical memorization (section 3.10)
6 Budget scaling 100k→250k→500k ✅ Completed (compare_budget_scaling.py → section 3.13) 24 runs (250k+500k × resnet18+mlp_vector × starpilot+dodgeball × 3 seeds); verdict: curves plateau — additional budget was unnecessary
7 Re-measure the rnd/ngu exploration arms ✅ Completed (24/09/2026) Corrected wrappers with dynamic stem probing and active bonus assertions re-measured across 40 runs (2 games × 4 arms × 5 seeds × 100k). Results in results/exploration_remeasure.json and results/eval100_results.json; verified that exploration parity with PPO stems from vanishing unnormalized bonus scale ($\sim 10^{-7}$/step), not code defects
8 Test curiosity at a non-inert bonus scale ✅ Completed (27/09/2026) 30 cells re-training icm/rnd/ngu with the intrinsic error divided by its trailing-window mean ($\beta$ left at 0.01, so scale is the only variable), lifting the injected signal from $\sim10^{-7}$ to $2.5$–$9.5\times10^{-3}$ per step. Every scaled arm beats the reused ppo control on stochastic return ($g$ 1.70–3.94; all three heist arms survive Bonferroni over the six comparisons), while the deterministic readout of the same weights stays $\le 0.70$ — section 3.6.1. Results in results/exploration_normalized.json and results/eval100_normalized.json

Final Scorecard (4 metric axes — Performance = re-eval 30 eps stoch, AUC section 3.9, Generalization = gen gap section 3.10, Robustness = ±std across seeds; sorted by Performance):

Model 🧠 Performance ⚡ AUC 🌎 Gap 🎲 Robustness
bossfight_resnet18 0.39 0.107 −0.12 ±0.50
bossfight_impala 0.33 0.141 −0.10 ±0.27
bossfight_lstm_attention 0.32 0.247 −0.20 ±0.47
bossfight_vit 0.29 0.064 −0.27 ±0.57
bossfight_impoola 0.25 0.186 −0.21 ±0.47
starpilot_lstm_attention 2.63 2.284 +0.15 ±0.44
starpilot_resnet18 2.53 2.325 −0.51 ±0.36
starpilot_impoola 2.44 2.230 −0.19 ±0.68
starpilot_impala 2.00 2.336 +0.31 ±0.45
starpilot_vit 1.88 2.297 −0.07 ±0.68
dodgeball_resnet18 1.07 1.175 +0.27 ±0.40
dodgeball_impoola 1.05 1.151 −0.07 ±0.31
dodgeball_impala 1.04 1.169 −0.11 ±0.20
dodgeball_lstm_attention 0.99 1.114 +0.24 ±0.23
dodgeball_vit 0.91 1.032 −0.21 ±0.35
maze_icm 2.80 3.658 +0.80 ±0.98
maze_ppo=rnd=ngu 2.47 3.785 +0.73 ±1.17
heist_icm 0.73 1.792 −0.47 ±0.71
heist_ppo=rnd=ngu 0.67 1.826 0.00 ±0.73

Scorecard — Suite Retrain (section 3.11; Performance = mean across 3 games; AUC unavailable — legacy logs pruned; aug_noise ≡ wm_contrastive, see note):

Model 🧠 Performance ⚡ AUC 🌎 Gap 🎲 Robustness
cnn_mlp_vector 1.36 — −0.01 ±0.41
cnn_spatial 1.35 — −0.02 ±0.38
aug_crop 1.31 — −0.17 ±0.44
cnn_cbam 1.24 — −0.26 ±0.54
cnn_classic 1.16 — −0.16 ±0.46
wm_ae 1.13 — −0.02 ±0.23
wm_recon 1.05 — −0.25 ±0.33
wm_contrastive=aug_noise 1.04 — −0.14 ±0.52
aug_color 1.02 — −0.06 ±0.48
wm_vae 0.92 — +0.11 ±0.32 (n=2–3)

Note: ContrastiveNoise subclasses ContrastiveExtractor without altering the forward pass (compare_augment_contrastive.py:42) — under identical seeds, it is an exact duplicate of wm_contrastive (identical empirical results across all 3 games). Treat as 9 independent configurations, not 11. Note: the ppo=rnd=ngu equalities in the 🧠/⚡/🌎/🎲 rows above are not measured ties — rnd/ngu ran with their intrinsic bonus disabled by a network-geometry bug that was silently caught (section 3.6). Those two arms need re-measurement. Cross-sectional analysis (unified data, 275 models under the new protocol): Global top-4: mlp_vector 1.36 > spatial 1.35 > lstm_attention 1.34 > aug_crop 1.31 — all statistically indistinguishable (section 3.8). starpilot_lstm_attention remains the sole configuration leading all 4 scorecard axes within a single game; now tied in Performance with cnn_spatial (2.63). resnet18 concedes the top spot of dodgeball to cnn_mlp_vector (1.21 vs 1.07). ICM leads maze/heist in final reward but trails in AUC. Generalization gap is ≈ 0/negative across the vast majority — no memorization (exception: maze +0.7~+0.8). 📌 Note: Values above correspond to the 30 eps protocol; the definitive 100 eps protocol (section 3.12) reinforces mlp_vector at the top (1.43), restores resnet18 to the top of dodgeball, and equates ICM with vanilla PPO in maze/heist.


7. Results per Seed (Complete) — 5 Seeds 42-46, 10 Eps deterministic=False

Coinrun 50k 5 Seeds logs_procgen/comparison_coinrun_20260827_204023/comparison_results.json:1

Config 42 43 44 45 46 Mean±Std
classic 7.0 6.0 7.0 9.0 9.0 7.6±1.2
cbam 8.0 8.0 8.0 8.0 8.0 8.0±0.0
spatial 6.0 0.0 7.0 9.0 9.0 6.2±3.31
mlp_vector 7.0 7.0 8.0 9.0 9.0 8.0±0.89

Bossfight 100k 5 Seeds (World Models) logs_world_models/comparison_bossfight_20260827_193327/comparison_results.json:1

Config 42 43 44 45 46 Mean±Std
vae 0.0 0.0 0.1 0.4 0.4 0.16±0.16
ae 0.0 0.0 0.1 0.1 1.3 0.30±0.50
recon 0.0 0.0 0.0 0.0 0.1 0.02±0.04
contrastive 0.0 0.0 0.0 0.2 1.6 0.36±0.62

Bossfight HARD 100k 5 Seeds logs_bossfight_hard/comparison_bossfight_hard_20260828_072148/comparison_results.json:1

Config 42 43 44 45 46 Mean±Std
vae 0.0 0.0 0.4 1.2 0.6 0.43±0.54
ae 0.1 0.1 0.4 0.1 1.2 0.38±0.41
recon 0.0 0.0 0.0 0.0 0.0 0.0±0.0
contrastive 0.0 0.0 0.0 0.0 0.3 0.06±0.12
classic 0.0 0.0 0.0 0.0 0.1 0.02±0.04
cbam 0.0 0.0 0.0 0.0 0.1 0.02±0.04
spatial 0.0 0.0 0.0 0.0 0.1 0.02±0.04
mlp 0.0 0.0 0.0 0.1 1.2 0.26±0.47
aug_crop 0.0 0.0 0.0 0.3 1.3 0.32±0.49
aug_color 0.0 0.0 0.0 0.0 0.1 0.02±0.04
aug_noise 0.0 0.0 0.0 0.0 0.3 0.06±0.12

Suite 100k 3 Games (11 configs × 3 × 5 = 165 entries) logs_suite/suite_bossfight_starpilot_dodgeball_20260827_204109/suite_results.json:1 — summary mean in suite_statistics.json:1; e.g., starpilot spatial: [1.6, 2.1, 2.2, 3.2, 3.5] 2.6±0.82, dodgeball classic: [0.8, 1.0, 1.4, 1.5, 2.7] 1.48±0.69. Full 165 raw entries preserved in suite_results.json for LaTeX compilation.

New Archs 100k 3 Games (5 × 3 × 5 = 75 entries) logs_new_archs/new_archs_bossfight_starpilot_dodgeball_20260828_134545/comparison_results.json:1

Config 42 43 44 45 46 Mean±Std
bossfight_impala 0.0 0.2 0.0 0.0 1.2 0.28±0.47
starpilot_lstm_attention 2.3 2.9 1.4 2.7 2.9 2.44±0.56
dodgeball_resnet18 1.2 1.6 1.4 3.0 1.4 1.72±0.65

Maze+Heist 100k 2 Games (4 × 2 × 5 = 40 entries) results/exploration_remeasure.json:1 (re-measured 24/09/2026, 10 eps unseen)

Config 42 43 44 45 46 Mean±Std
maze_ppo 2.0 2.0 1.0 1.0 3.0 1.80±0.84
maze_icm 3.0 2.0 0.0 1.0 5.0 2.20±1.92
maze_rnd 2.0 4.0 0.0 0.0 6.0 2.40±2.61
maze_ngu 2.0 2.0 0.0 1.0 6.0 2.20±2.28
heist_ppo 1.0 1.0 1.0 0.0 1.0 0.80±0.45
heist_icm 1.0 0.0 2.0 0.0 1.0 0.80±0.84
heist_rnd 2.0 1.0 3.0 0.0 2.0 1.60±1.14
heist_ngu 1.0 0.0 3.0 0.0 2.0 1.20±1.30

8. Videos

Rendered on demand via visualize_side_by_side.py (commands documented in section 4.8): bossfight_dreams.mp4 (World Models with reconstructed dreams) and coinrun_side_by_side.mp4 (CNN vs MLP side-by-side rollout). Output is formatted as .mp4 with 128×128 panels per agent stacked horizontally (hstack) at 15 FPS; latent decoding (dream()) implemented for VAE/AE/Recon (models/world_model_extractors.py dream() deconv), while Contrastive displays no dream. Requires .zip checkpoints produced during benchmark runs.

No video files are committed: results/ has never contained an .mp4 in this repository's history, so both names above are outputs you generate locally. The only committed dream artifacts are jax_port/dreams/bossfight_dreams.gif and jax_port/dreams/dreamer_imagined.gif. Note also that until the 23/09/2026 extractor fix, AEExtractor.dream() decoded through a second encoder branch that forward() never used, so an "AE dream" panel from the old code visualized an untrained path; a failed decode now renders a panel labelled dream unavailable instead of an unlabeled black frame.

9. Hardware and Limitations

  • Hardware: NVIDIA GeForce RTX 4070 Laptop, driver 556.29, CUDA 12.5, WDDM, 8 GB VRAM, 58°C, ~30% GPU-Util during PPO execution on cuda (nvidia-smi sampled at 20:41), Python 3.10.11, torch 2.5.1+cu121, gym 0.26.2, gymnasium 1.3.0, stable-baselines3 2.9.0.
  • Limitations: In bossfight 100k, easy achieves <1.0 (0.76±0.90) and hard collapses standard CNNs to 0.02±0.04 — suite 100k (16.5M steps) is the minimum viable scale for statistical ranking; ViT evaluated in benchmark #6: ~4.6 min/run in bossfight but 16-25 min/run in starpilot (~4× slower than standard CNNs, varying per game environment) — earlier estimates of ~15× (Imitation-player:171) stemmed from imitation learning loss computation rather than PPO rollouts; nonetheless, ViT 1.20 global fails to surpass lstm or spatial; Imitation BC only tracked training loss (ResNet 2.90 best) without interacting with RL reward signals — benchmark #4 rectifies this.

10. References

  • Procgen (Cobbe et al.), DreamerV3 (Hafner et al., SheepRL), CURL/SPR (mario-ds:121), CBAM (Woo et al.), PPO (Schulman et al.), Stable-Baselines3, Imitation-player compare_models.py Nature 3.48 vs ResNet 2.90.

11. Independent Benchmark — HRL vs Flat RL (jumper/plunder)

⚠️ Separated from the primary study: Not incorporated into the scorecard of sections 3.x/6.1; tracked under dedicated logs/results (logs_hrl/, results/hrl_results.json).

Research Question: Does hierarchical temporal abstraction provide measurable leverage under a 100k primitive frame budget? Four arms evaluated under an identical budget of 100k primitive environment frames, utilizing identical PPO optimization (lr 3e-4, NatureCNN, matched hyperparameters):

Arm Description Trained Decision Steps
flat Standard PPO over all 15 primitive actions 100k
skip4 Fixed action-repeat 4 (control: temporal abstraction without hierarchy) 25k
hrl PPO meta-controller selecting across 6 fixed sub-skills executed for 4 frames (options framework, compare_hrl.py:34) 25k
hrl_learned Jointly trained two-level hierarchy (compare_hrl_learned.py): meta-policy selects among 6 learned latent skills every 4 frames; low-level policy π(a|obs, z) executes primitive actions; emergent specialization without explicit diversity incentives 25k meta + 100k low

Fixed Skills for the hrl Arm (derived from empirical action space probing in probe_actions.py and official Procgen action mappings in procgen/env.py; UP=jump in jumper, D=shoot in plunder): wait, left, right, jump|shoot, jump_left|shoot_left, jump_right|shoot_right.

Comparability Notes: hrl_learned receives per-frame updates at the low level (gradient throughput comparable to flat) and utilizes truncation with bootstrapping at HORIZON=256 frames (episodes in jumper exceed 500; without this horizon ceiling the meta-policy would make too few decisions) — remaining arms operate on native episodic terminations. Models saved as .pt (meta+low-level weights) under logs_hrl/hrl_zips/, rather than SB3 .zip.

Protocol: 2 games × 4 arms × 5 seeds = 40 runs (30 from compare_hrl.py + 10 from compare_hrl_learned.py, executed sequentially); training on num_levels=200 easy; definitive evaluation on 100 eps stochastic + 100 eps deterministic (unseen seed+1000) + 15 eps training. Data in results/hrl_results.json, analysis in results/hrl_analysis.json (hrl_analysis.py).

11.1. Results (Completed: 40/40, 0 errors)

Arm jumper stoch jumper det plunder stoch plunder det
flat 0.90±0.49 0.38 3.53±0.45 0.61
skip4 3.76±0.48 0.38 3.36±0.28 1.32
hrl (fixed) 3.72±0.86 0.66 3.24±0.14 0.85
hrl_learned 2.96±0.45 0.44 4.16±0.33 2.70

4 Findings: (1) jumper: Gain stems purely from temporal abstraction, not hierarchy — skip4 ≈ hrl (3.7) achieve 4× the return of flat (0.90); sustaining directional jumps over 4 consecutive frames is what solves the game mechanics, and the fixed skill library added zero benefit beyond action-repeat. (2) hrl_learned ranks between flat and temporal-abstraction arms in jumper (2.96) — co-training meta and low-level controllers requires a larger sample budget to match hand-engineered macro-actions. (3) plunder: hrl_learned is the sole winning arm (4.16 vs 3.53 flat, with lowest standard deviation 0.33) — fixed 4-frame firing macros disrupt aiming timing, whereas learned latent skills adapt flexibly; fixed macro arms merely match flat. (4) Deterministic evaluation collapses policies in jumper (0.38–0.66, stochastic exploration is vital), but in plunder, hrl_learned stands out (det 2.70 vs ≤1.32) — learned hierarchy produces a more deterministically exploitable policy. Generalization gap for plunder hrl_learned is +0.97 (the only non-trivial positive gap).

11.2. Action-Repeat Duration Sweep ($k \in {1, 2, 4, 8}$) — Disentangling Physical Inertia from Hierarchy (compare_action_repeat.py, jax_port/bench_action_repeat.py)

A critical theoretical question emerged from the HRL benchmark (Section 11.1): Was the observed advantage of skip4 and hrl purely an artifact of physical inertia and reduced decision frequency, or does hierarchical structure provide genuine inductive value?

To isolate these factors, we executed an action-repeat parameter sweep across $k \in {1, 2, 4, 8}$ without hierarchy. All arms operated under a fixed budget of $100{,}000$ primitive frames ($N = 100{,}000 / k$ updates), across 3 seeds (42, 43, 44) on both jumper and plunder, evaluated on unseen levels (seed+1000):

Game $k=1$ (Flat) $k=2$ $k=4$ $k=8$ hrl_learned (Section 11.1)
jumper (stochastic) 3.78±0.42 3.56±0.57 3.11±0.63 1.44±1.10 2.96±0.45
jumper (deterministic) 1.11±0.54 1.11±0.82 0.89±0.42 0.78±0.16 0.44
plunder (stochastic) 3.91±0.80 3.73±0.56 3.16±0.45 0.57±0.80 4.16±0.33
plunder (deterministic) 1.68±0.20 1.63±0.19 1.64±0.18 0.19±0.13 2.70

Empirical Findings:

  1. Temporal Over-Commitment Collapse ($k=8$): In both environments, repeating actions for $k=8$ consecutive frames causes policy collapse (jumper $1.44$, plunder $0.57$). In fast-paced games, an 8-frame ballistic commitment eliminates fine-grained obstacle avoidance and precise projectile interception.
  2. jumper Mechanics: Flat PPO ($k=1$) trained with dense decision steps achieved $3.78 \pm 0.42$, matching $k=2$ ($3.56$) and $k=4$ ($3.11$). In jumper, temporal commitment over $2-4$ frames maintains jumping momentum, but excessive commitment ($k=8$) leads to overshooting platforms.
  3. Definitive Isolation of Hierarchy in plunder: In plunder, hrl_learned ($4.16 \pm 0.33$ stoch, $2.70$ det) strictly outperformed EVERY pure action-repeat duration ($3.91$ at $k=1$, $3.73$ at $k=2$, $3.16$ at $k=4$, and $0.57$ at $k=8$). This conclusively demonstrates that the performance advantage of learned hierarchical options in plunder cannot be attributed to action repeat or physical inertia; rather, the low-level latent policy acquires adaptive, condition-dependent maneuvering and firing primitives that outperform static temporal repetition. Data serialized in results/action_repeat_results.json.

12. Independent Benchmark — Value-Based vs Policy-Based (starpilot/dodgeball/bossfight)

⚠️ Separated from the primary study (which compared architectures with fixed PPO): Here, the independent variable is the algorithmic optimization family. Tracked in separate directories (logs_algo/, results/algo_families_results.json).

Research Question: In a 100k step visual regime, does on-policy policy gradient outperform off-policy value bootstrapping?

Family Algorithms Characteristics
Policy-based PPO (study hyperparameters), A2C (SB3 default, lr 3e-4) On-policy, 100k effective decision update steps
Value-based DQN, QR-DQN (sb3-contrib, distributional with 200 quantiles) Off-policy, replay buffer

Fairness Controls: Identical visual backbone for all configurations (CnnPolicy/NatureCNN 512D); 3 games × 4 algorithms × 5 seeds = 60 runs; definitive evaluation (100 eps stochastic + 100 eps deterministic unseen seed+1000 + 15 train).

Documented Adaptations for Small Budgets (compare_algo_families.py:32): buffer_size=100k (standard 1M overflows system RAM with raw image buffers), learning_starts=5000 and exploration_fraction=0.25 (standard Atari defaults of 50k/10% would consume half of or bypass the budget entirely), lr=1e-4 (DQN standard; 3e-4 destabilizes TD-error updates), train_freq=4, gradient_steps=1, target_update_interval=500, batch=64.

⚠️ Fairness Limitation (Design Decision): The comparison applies best-practice defaults per algorithm family (lr 3e-4 policy vs lr 1e-4 value), rather than an identical configuration. Imposing 3e-4 uniformly risked measuring "DQN diverges under excessive learning rates" rather than assessing the value-based paradigm itself. In the absence of an exhaustive learning rate sweep, conclusions remain conditioned on standard defaults — the sensitivity test lr_sensitivity.py (section 12.2: dqn/qrdqn trained at 3e-4 on starpilot, the game exhibiting the widest gap, 10 runs) verified whether learning rate disparity explained the deficit. Empirical outcome (section 12.2): It does not — dqn 0.65→0.66, qrdqn 1.04→1.25 (well within standard error).

Status: Completed (60/60, 0 errors, runtime ~9h from 01/09 20:09 to 02/09 05:12). Data in results/algo_families_results.json, analysis in results/algo_families_analysis.json (algo_analysis.py).

12.1. Results (Stochastic Unseen 100 eps, Mean ± Std Across 5 seeds)

Game ppo a2c dqn qrdqn
starpilot 2.29±0.47 2.38±0.29 0.65±0.37 1.04±0.26
dodgeball 0.65±0.36 0.89±0.10 0.18±0.09 0.61±0.49
bossfight 0.18±0.29 0.05±0.06 0.03±0.05 0.28±0.50
Family (3-Game Average) policy 1.07 value 0.47

4 Findings: (1) Policy-based algorithms decisively outperform value-based (1.07 vs 0.47, a 2.3× margin) — in 100k visual steps, on-policy optimization outperforms value bootstrapping, validating the low-data hypothesis (section 1.4). (2) QR-DQN significantly closes the gap (+60% over vanilla DQN in starpilot: 1.04 vs 0.65) and in bossfight (highly sparse, high variance) it is the sole leading algorithm (0.28 vs ppo 0.18) — distributional RL provides tangible benefits precisely under high aleatoric/epistemic uncertainty. (3) A2C matches or exceeds PPO in starpilot/dodgeball (2.38/0.89 vs 2.29/0.65), but collapses in bossfight (0.05 vs 0.18) — without policy clipping, gradient outliers in sparse reward regimes destabilize policy weights; PPO's clipped surrogate objective proves essential where stability is critical. (4) Generalization gap is ≈0 across all 12 evaluation branches — neither algorithmic family overfits the training levels (consistent with sections 3.10/3.13). Context note: Absolute returns differ from section 3.12 because a uniform NatureCNN extractor was employed across all algorithms here (the primary study evaluated bespoke extractors) — comparisons remain fully valid within this benchmark.

12.2. Learning Rate Sensitivity Test — Is the Deficit an Artifact of 1e-4? (lr_sensitivity.py, Completed: 10/10)

dqn/qrdqn retrained at 3e-4 (matching PPO/A2C) on starpilot (the largest gap game), 5 seeds, identical protocol. Data in results/lr_sensitivity_results.json:

Algorithm lr 1e-4 (section 12.1) lr 3e-4 Δ Policy Baseline Reference
dqn 0.65±0.37 0.66±0.44 +0.01 (identical) ppo 2.29 / a2c 2.38
qrdqn 1.04±0.26 1.25±0.37 +0.21 (within noise) idem

Verdict: Tripling the learning rate of value-based methods does not alter the conclusion. DQN remains virtually identical (0.65→0.66); QR-DQN gains +0.21, which falls within seed standard error (SE of the difference ≈0.20 with n=5) — a minor, non-significant trend. Both remain ~2× below PPO/A2C (2.29/2.38). The fairness limitation raised in section 12 is thus empirically resolved: the policy-vs-value gap at 100k is not an artifact of learning rate selection (at least in starpilot, the evaluated environment).


13. Scope and Restoration Note (04/09/2026) — Why this README was Restored

13.1. Background Context

This repository transitioned through three distinct phases, all preserved within Git commit history for auditing:

  1. ProcGen/SB3 Phase (this study, commit 4f84ed3). The original systematic benchmark on real ProcGen using Stable-Baselines3/PyTorch — sections 1–12 above. This constitutes the scientific core of the project.
  2. JAX/Craftax/Brax/MARL Phase (commits 5e88c16–6d450fa). An exploration rewriting environments in JAX/Flax/Optax featuring Craftax, Brax, MPE, offline boxing, and combinatorial grids. Protocols, budgets, metrics, and dynamics differed substantially from the ProcGen study (e.g., symbolic Craftax episodic return vs pixel ProcGen reward; 8M steps vs 100k; throughput measured on non-comparable workloads).
  3. JAX-Port-of-ProcGen Phase (Sep/2026). A systematic initiative to reconstruct the exact study in JAX to reproduce the identical experimental design at substantially higher throughput (detailed in section 14).

On 04/09/2026, by explicit design decision, all phase 2–3 exploration scaffolding outside the study scope was removed from the working tree, and the repository was restored to the exact state of commit 4f84ed3. Sections 1–12 above are, therefore, the byte-for-byte documentation of the original ProcGen study — no numbers, tables, or conclusions were altered in this restoration.

13.1.1. Methodological Rationale for Returning to ProcGen

Through empirical profiling, training wall-clock time in Craftax was found to be virtually identical to ProcGen (runs requiring millions of steps, multi-hour grids), yet with significantly reduced academic depth — lacking the published generalization protocol (200 train vs 0 unseen), lacking the 16 architectures × 5 games × 5 seeds grid, and without CI/Cohen's d/AUC/dual-eval/budget-scaling rigor. Computational resources were being expended for a fraction of the scientific rigor. Consequently, the optimal methodological path was returning to ProcGen and accelerating it — maintaining the rigorous study while resolving its sole bottleneck (throughput) via a faithful JAX port, rather than compromising on experimental depth. Sections 14–16 document this work.

13.2. Academic Justification for Scaffolding Removal

Removal is framed not as lost effort, but as an essential measure of internal validity:

  • Construct Validity. Aggregating two benchmarks with divergent reward semantics, episode horizons, and budgets (Craftax 8M vs ProcGen 100k) within a single README/results/ invites invalid comparisons between incommensurable quantities. Generalization literature in RL (Cobbe et al., ProcGen) mandates a fixed protocol per study.
  • Reproducibility. The ProcGen study relies strictly on Python ≤3.10 + procgen 0.10.7 + gym 0.26.2 + torch cu121 (section 5 and requirements.txt), whereas the exploratory JAX suite required Python 3.12 + jax 0.11 + craftax/brax. Maintaining both within the same virtual environment breaks dependency resolution on both sides (the numpy<2 vs numpy 2.x conflict in section 14.3 is a direct example).
  • Traceability. All excised code remains fully retrievable via Git history (git log --oneline, git show 5e88c16:..., git show 6d450fa:...). History was not rewritten; the working tree was simply aligned with the study's scope.

13.3. Inventory of Restored and Archived Components

Archived from the working tree (preserved in Git history): src/ (JAX trainers: ppo.py, dqn.py, marl_*, continuous_rl.py, offline_rl.py, recurrent_ppo.py, eval_utils.py, procgen_parity_modules.py, procgen_env.py, procgen_ppo.py), experiments/ (12 JAX/Craftax/Brax/MARL benchmarks), figures/ (9 JAX figures), results/ JAX artifacts (*_benchmarks_results.json, boxing_*, dataset_boxing_expert.npz, procgen_parity_master_results.json, combinatorial_grid_results.json, results/logs/, boxing_final_results.txt), standalone scripts (run_all_procgen_combinations.py, run_combinatorial_grid.py, run_convergence_*.sh, run_full_benchmark.py, run_smoke_test.py, bench_*.py, smoke_procgen.py, diag_env.sh, probe_procgen.sh, setup_procgen_env.sh, _verify_pg.py, _probe_pg3.py), and build caches (.jax_compile_cache/, __pycache__/).

Restored to state 4f84ed3: All root compare_*.py scripts, models/ (sb3_extractors.py, cnn_attention.py, cnn_classic.py, combined_extractors.py, world_model_extractors.py), procgen_wrapper.py, *_analysis.py, re_eval_*.py, visualize_*.py, pinned requirements.txt (ProcGen), original .gitignore, and original results/ (JSONs + PNGs for sections 3–12).

Accuracy note (23/09/2026): "preserved in Git history" holds for the bulk of the inventory — git log --all --diff-filter=A --name-only confirms 29 src/ modules, 20+ experiments/ benchmarks and the JAX results/*.json artifacts are retrievable (e.g. git show 5e88c16:src/ppo.py). It does not hold for every name in the list above: the throwaway helper scripts setup_procgen_env.sh, probe_procgen.sh, diag_env.sh, smoke_procgen.py, _verify_pg.py and _probe_pg3.py, plus boxing_final_results.txt and combinatorial_grid_results.json, were never committed and are gone. They were scratch tooling and one duplicate of combinatorial_matrix_results.json, so nothing in sections 1–19 depends on them.

13.4. Purpose of Sections 14–16

Sections 1–12 remain frozen as the scientific record of the completed study. Sections 14–16 serve as the methodological research log: documenting, with identical academic rigor, the trajectory, decisions, architectural choices, and adaptations encountered while porting this study to JAX — including verified milestones (Gates PA0 and PA1 cleared, §14.3 and §15.1), discarded paths, and open objectives. No metrics in sections 1–12 are altered here; this constitutes meta-documentation of process, not new study outcomes.


14. Trajectory of the JAX-Port-of-ProcGen — Objective, Decisions, and Gate PA0

14.1. Core Objective and Selected Route (Path A)

The objective was never "accumulate disparate benchmarks in JAX", but rather "the ProcGen study, executed faster". Among possible alternatives, Path A — faithful porting of the ProcGen study to JAX — was explicitly chosen:

Reconstruct the exact study in JAX: Authentic ProcGen (envpool/CPU) + 16 architectures × 5 games × 5 seeds × 100k steps + 95% CI / Cohen's d / AUC / dual-evaluation (stoch+det) / budget-scaling. Expected speedup: 10× over SB3 throughput (300 FPS legacy on cuda), preserving identical experimental design.

Rejected alternatives included: (B) substituting ProcGen with JAX/Craftax — rejected because symbolic/pixel Craftax and pixel ProcGen possess divergent observation spaces, transition dynamics, and generalization regimes, meaning "PPO ≫ DQN on Craftax" fails to answer "which visual extractor generalizes best on ProcGen"; and (C) reimplementing ProcGen natively in pure JAX — rejected as intractable (ProcGen is C++/OpenGL with proprietary procedural generation; rewriting it introduces a different environment rather than accelerating the existing benchmark).

Path A necessitated a hybrid architecture: CPU environment pipeline + JAX GPU learner — vectorized ProcGen environments hosted on CPU feeding a batched PPO learner implemented in JAX on the GPU.

14.2. Phased Roadmap and Resource Constraints

Prior to implementing learner logic, a phased plan with strict viability gates was established:

  • PA0 (Compatibility): Prove that procgen 0.10.7 and jax[cuda12] coexist and operate (env simulation + GPU acceleration) within the same interpreter environment. Exit criterion: PROCGEN_JAX_OK — initialize headless coinrun and execute a JAX kernel on cuda:0 within the same virtual environment.
  • PA1 (Throughput): Construct the vectorized CPU→GPU pipeline and measure empirical FPS against the ~300 FPS SB3 legacy baseline.
  • PA2+ (Fidelity): Implement the 16 extractors and the full evaluation suite (CI/Cohen/AUC/dual-eval/budget-scaling), validating numerical parity on a subset before launching the full grid.

As a prerequisite for PA0, running background workloads were safely terminated (caching intermediate JSONs) to release GPU VRAM, and the host environment was profiled with diag_env.sh. Maintaining a single concurrent job on the 8 GB GPU prevents VRAM out-of-memory errors.

14.3. Gate PA0 — Diagnostics, Roadblocks, Probing, and Resolution

Initial Diagnostic (Informative Failure). The active environment had Python 3.12 + JAX 0.11.1 + operational cuda:0, but pip could not locate any compatible wheel for procgen: procgen 0.10.7 distributes wheels up to cp310. The host lacked Python 3.10 or conda toolchains, and ProcGen requires compiled C++ extensions. Compiling from source without cmake was error-prone. Resolving environment compatibility was a non-negotiable prerequisite.

Feasibility Probing (probe_procgen.sh). Two empirical questions were addressed: (1) Does a valid manylinux wheel for procgen 0.10.7 exist for cp310? Yes — procgen-0.10.7-cp310-cp310-manylinux...whl installs cleanly without local compilation. (2) Can Python 3.10 be acquired on Ubuntu 24.04 via reputable packaging channels? Yes, via the deadsnakes PPA. Verdict: VIABLE.

Environment Setup (setup_procgen_env.sh). Executed pipeline: Python 3.10 (deadsnakes) + dedicated virtual environment + procgen (wheel) + jax[cuda12]. Interpreter and venv configuration completed rapidly; JAX CUDA wheel installation was the primary I/O bottleneck. Initial validation revealed two specific issues, resolved and documented below:

# Diagnostic Symptom Root Cause Implemented Resolution
1 numpy ABI conflict: procgen requires numpy<2.0, but default JAX installation pulled numpy 2.2.6 Divergent dependency bounds (legacy C++ extension vs modern JAX ecosystem) Pinned numpy==1.26.4 in the port venv; residual ml-dtypes warning is harmless (GPU test kernel executed successfully)
2 Initial test script targeted gymnasium, but ProcGen registers within gym 0.26.2 (legacy 4-tuple step API) Original study bridges this via wrapper (procgen_wrapper.py); probe script attempted direct lookup in the wrong namespace Updated verification script (_verify_pg.py) to legacy gym API; explored vectorized C++ interface in _probe_pg3.py (gym3)

Validation (Gate Cleared, Re-verified on 04/09/2026). Following adjustments, PROCGEN_JAX_OK was achieved in headless WSL2 under py3.10: coinrun initializes with observation shape (64,64,3) uint8 and Discrete(15) action space, and JAX executes on cuda:0 (verified: jax 0.6.2, devices=[CudaDevice(id=0)], matmul reduction produces 1073741824.0). Verified stack: JAX 0.6.2 + flax 0.10.7 + optax 0.2.8 + numpy 1.26.4 + procgen 0.10.7 (with gym 0.26.2, gymnasium 1.3.0, gym3 0.3.3).

14.4. Summary of Design Decisions and Technical Adjustments

Decision / Adjustment Alternative Considered Criteria and Outcome
Path A (faithful port) vs B (Craftax substitute) vs C (native JAX rewrite) Path B was computationally cheaper; C was "pure JAX" Experimental fidelity took priority: only Path A addresses the research question without altering environment dynamics
Terminate Craftax grid prior to PA0 Run setup concurrently 8 GB VRAM cannot safely sustain concurrent GPU training and compilation; clean termination with serialized JSON
deadsnakes + dedicated py3.10 venv Compile ProcGen under py3.12, or conda/pyenv cp310 wheel is pre-built; dedicated venv isolates the numpy version conflict from the SB3 study environment
Pin numpy 1.26.4 Force numpy 2.x Strict requirement of ProcGen C++ extension (<2); JAX operates nominally on 1.26
Validate via gym 0.26.2 (and probe gym3) rather than direct gymnasium Uniformly enforce gymnasium ProcGen environment registry is natively gym; the port preserves the established wrapping boundary
Archive exploratory JAX scaffolding from working tree (section 13) Keep setup scripts alongside main study Cleanliness of repository and dependencies: SB3 study remains reproducible with a single requirements.txt; scaffolding is preserved in Git history

15. Current Status, Boundaries, and Full Grid Completion (PA2-Speed Complete; Parity Complete)

Current Status. The primary working tree remains the fully reproducible ProcGen/SB3 study (sections 1–12; reproduction commands in section 5). The JAX port, co-located on main under jax_port/ (without modifying root study files), spans the full experimental design: 13 backbones, PPO/A2C, DQN/QR-DQN, ICM/RND/NGU, data augmentations, 4-arm HRL, stoch+det+gap evaluation, statistical metrics (CI/Cohen/AUC), and the full 615-cell multi-seed parity grid — completely executed and serialized in jax_port/analysis_full.json (§15.4.3).

Evidence note (23/09/2026): the raw per-cell grid outputs are git-ignored, so jax_port/cells_summary.json (897 cells = 865 evaluated + 32 MARL) is the committed record they were reduced to. python -m jax_port.analyze_grade --from_summary jax_port/cells_summary.json regenerates analysis_full.json from that record alone, and tests/test_grid_analysis.py fails if the two drift apart. analyze_grade.py resolves its own paths and refuses to overwrite a richer analysis with a thinner one (previously a fresh clone silently rewrote the aggregate into an empty file). The top-1-vs-top-2 overlap flag in jax_port/stats.py is also corrected: its condition was inverted in a way that reported non-overlap almost always, overstating significance — the rankings themselves are unchanged and the regenerated aggregate matches the published one.

15.1. PA1 — Pipeline and Measured Throughput (04/09/2026, venv /root/procgen-jax, WSL2, coinrun, Random Actions, 3000 Steps)

Reproducible command (executed via WSL, without torch/sb3/cv2 dependencies in the port venv):

wsl -e env PYTHONPATH=/mnt/c/Users/Acer/Downloads/MLE \
  /root/procgen-jax/bin/python \
  /mnt/c/Users/Acer/Downloads/MLE/jax_port/bench_throughput.py \
  --game coinrun --num-envs 1 4 16 --steps 3000 --seed 42 \
  --out /mnt/c/Users/Acer/Downloads/MLE/jax_port/pa1_throughput.json
num_envs Raw Env FPS (CPU, autoreset) Env→GPU FPS (Transfer + JIT uint8→float32 /255 with step synchronization)
1 12,618.2 301.5
4 13,272.9 910.1
16 18,695.5 3,023.8

Source: jax_port/pa1_throughput.json. Standard gym/np.bool8 deprecation warnings during execution are expected due to legacy ProcGen bindings.

Empirical Interpretation: (1) Raw CPU simulation exhibits sub-linear scaling from 1→16 envs (12.6k→18.7k) due to Python single-process synchronous step loops; multi-process C++ wrappers (gym3) offer headroom. (2) Step-by-step synchronization (asarray + JIT + block_until_ready per step) dominates overhead: with 1 env, the pipeline sustains 300 FPS, matching the legacy SB3 training baseline (300 FPS, section 1.4) — but here without network updates. (3) Vectorized batching amortizes host-to-device synchronization: 16 envs yield ~3k FPS pre-learner, representing a ~10× headroom margin before accounting for PPO backpropagation. Path A throughput was subsequently measured with active gradient updates (§15.2), surpassing this 3k benchmark.

Grid Execution Status. Full execution of run_grade.py across 10 suites with 5 seeds and --eval-full was completed on 06/09/2026 (615/615 cells completed), benchmarking ranking consistency against sections 3–12 and confirming statistical parity within inter-seed variance (§15.4.3).

15.4. Comprehensive Port — Coverage Map and Test Evidence (05/09/2026)

Each component of the original study corresponds to a JAX module in jax_port/, with test coverage:

Primary Study (sections 1–12) JAX Port Implementation Executed Verification Test
16-Architecture Suite (compare_suite/new_archs/combined, §3) backbones.py (13 extractors) + train.py --extractor Zoo 13/13 shapes+parameters (§15.4.1); smoke training 8/8; mini-grid 16 cells OK
mlp_vector (procgen_wrapper.py:55, §3.12) --obs vector mode (luminance + mean pooling, no cv2 dependency) Smoke training verified; test_smoke suite uses this pipeline
Augmentations: crop/color/noise (§3.3/3.11) augment.py (p=0.5/float) + --augment Smoke crop and contrastive+noise verified
ICM/RND/NGU (compare_maze_heist.py:16, §3.6) exploration.py (beta=0.01, online/step, inverse dynamics behavior documented) Smoke maze-ICM/RND/NGU + heist-RND verified
LSTM-Attention stateless (§3.5) LSTMAttention (repeat-4 + BiLSTM + MHA) Output shapes and smoke training verified
VAE/AE/Recon/Contrastive (§3.2) VAEBackbone (reparameterization) + twins ae/recon ≡ classic + contrastive ≡ classic+noise Smoke vae/ae/contrastive+noise verified
PPO/A2C/DQN/QR-DQN (§12) train.py --algo, dqn.py + train_dqn.py (100k buffer, eps 1→0.05 over 25%, lr 1e-4, QR-200, --lr sensitivity) Smoke dqn/qrdqn/a2c verified
HRL flat/skip4/hrl/hrl_learned (§11) train_hrl.py (exact skill mapping, DUR=4, frame-based budget, low-level π(a|obs, z)) Smoke across all 4 arms verified
Evaluation: 100+100+15, generalization gap (§3.10–12) --eval-eps/--eval-det-eps/--eval-train-eps + gen_gap across 3 trainers Regression tested across PPO/DQN/HRL
Statistics: CI/Cohen/AUC (§3.8–3.9) stats.py (Student's t, Cohen's d, trapezoidal integration) test_stats validated against analytical values
Grids + budget scaling + hard + pilot + extensions (§2–3.13) run_grade.py (suites: main/exploration/algo/hrl/budget/hard/pilot/spr/gnn/aux, --distribution, resume via master.json) + spr.py + contrast.py Mini-grid 26/26 cells verified (§15.4.1)
Controlled SB3 vs JAX Benchmark (§15.3) bench_sb3_paired.py + paired_*.json Arms A/B/C empirically profiled

15.4.1. Empirical Test Evidence (05/09/2026, /root/procgen-jax, RTX 4070)

  • test_stats: STATS_OK across both environments (Win py3.10 and Linux venv).
  • test_parity: gym3 batching matches single-instance gym step-for-step across 200 steps with 0 numerical discrepancies (PARITY_OK).
  • test_zoo: 13/13 forward passes pass shape checks; classic CNN parameter count matches at 608,944 (~600k in study); identical backbones verified (ZOO_OK).
  • test_smoke: End-to-end MLP training loop verified (SMOKE_OK).
  • Addressed bugs and edge cases: Flax default SAME padding inflating parameters to 2.1M (corrected to VALID); stop_gradient syntax; DQN entrypoint hook; device-side gather latency; np.trapezoid compatibility under numpy 1.26.
  • Mini-grid execution: 26/26 cells (HRL 4 + main 16 + algo 6) completed in ~6 min; resume functionality verified via master.json.
  • Test execution: wsl -e env PYTHONPATH=... /root/procgen-jax/bin/python -m jax_port.tests.run_tests (stats+parity+zoo+smoke, ~3 min); full grid executed via run_grade.py --suite <s> --games <g> --seeds 42-46 --timesteps 100000 --eval-full (10 suites, 615/615 cells completed, documented in analysis_full.json).
  • Path conventions (updated 23/09/2026): every jax_port/ module resolves its own package directory, so run_grade.py writes into jax_port/results_grade/ and analyze_grade.py / analyze_temporal_hard.py read and write inside jax_port/ regardless of the current working directory. No source file hardcodes /mnt/c/... or C:\Users\... any more (the literal commands quoted in sections 14-15 are kept as the historical record of what was executed); a WSL invocation is just PYTHONPATH=<checkout> <venv>/bin/python -m jax_port.<module>. The orchestration scripts take overrides instead of literals: marl_10m_run.sh / marl_2s3z_run.sh honor REPO, PY, SEEDS, LR, ENT; continue_grade.ps1 honors REPO and PROCGEN_JAX_VENV (default /root/procgen-jax). .gitattributes pins *.sh to LF because those scripts are executed inside WSL from the Windows checkout.
  • Figures: jax_port/figures/01-06.png (global CI, paired speedup, budget scaling, HRL, algorithm families, top-10 n=10) generated programmatically by make_figures.py directly from serialized JSON results.
  • hrl_learned at 500k Steps: On plunder across 3 seeds: learned achieves 4.19 vs fixed 3.42 (at 100k: 4.23 vs 3.19). Advantage persists stably; co-training sustains performance without divergence under 5× budget.
  • Dreams Reconstruction (VAE/AE): jax_port/dream.py (mirrored decoders with SAME padding for exact 64×64 reconstruction) — 20k frames on bossfight: VAE BCE 0.37 / KL 0.02, AE 0.32 (~30 s each); generated jax_port/dreams/dreams_panel.png + bossfight_dreams.gif (300 steps, structured visual dreams: pixel std 44–60 vs 57 ground truth). Rendered as GIF due to upstream imageio-ffmpeg audio argument incompatibility in the venv.
  • Full Dreamer 1M Steps: dreamer.py + train_dreamer.py — coinrun seed 42, 1M frames in 656 s (1,524 SPS, ~10× SB3; lower than PPO-JAX due to sequence-level world model updates and imagination rollouts). World model converged (dreamer_imagined.gif visual std 61.7 vs 0.7 at initialization), while policy actor plateaued at 0.0 return without reward symlog normalization.
  • Dreamer Actor Tuning (Sweep B/C/D): Baseline A (raw rewards, entropy 3e-4) returned 0.0; B (symlog rewards, entropy 3e-4) reached 3.0 return; C (symlog, entropy 1e-3) reached 2.5; D (raw, entropy 1e-3) reached 0.5. Verdict: Symlog reward transformation resolves the actor bottleneck; default updated in train_dreamer.py. Artifacts in sweep.json and jax_port/dreams/.
  • Dreamer reached by the grid runner (27/09/2026): the dreamer suite was wired into run_grade.py's cell builder on 25/09 but never added to --suite's argparse choices, so --suite dreamer exited with invalid choice and the branch had executed zero cells — results_grade/dreamer/ did not exist. The published Dreamer rows above come from train_dreamer.py directly, which is why the dead branch stayed invisible while looking integrated. With the choice registered, one cell was smoked end-to-end through the grid path (JAX_PLATFORMS=cpu, coinrun seed 42, 20k frames, 8 envs, 3 eval eps): 424 s at 47 SPS, train_ret_mean20 3.0, eval_unseen 3.33, and analyze_grade.py's Loader parses the cell it wrote (_cfg=dreamer, eval_unseen.mean present, 0 unreadable, 0 no-eval). The artifact is deliberately parked in results_grade/dreamer_smoke/ rather than results_grade/dreamer/: dreamer is in RANKING_SUITES, so a 20k-frame plumbing cell left in the graded directory would be folded into cells_summary.json and into everything downstream of it.

15.4.3. Full Grid Evaluation Verdict (05–06/09/2026, 615 Cells, Eval 100+100+15)

Full execution: main 240 + exploration 40 + algo 70 + hrl 40 + budget 60 + hard 55 + pilot 20 + spr 30 + gnn 15 + aux 45. Academic analysis:

  • Parity Confirmed on Core Findings. Global top-6 clustered at 1.32–1.24 with overlapping confidence intervals (d=0.27) — replicating the primary study finding (top tier statistically indistinguishable, §3.12). Algorithm families replicate closely: policy-based > value-based across all 3 games, qrdqn leads bossfight, a2c ≈ ppo, learning rate sensitivity is minimal. On plunder, hrl_learned leads (4.23), matching the study. Exploration shows an empirical tie: ppo ≈ icm ≈ rnd ≈ ngu, replicating the definitive protocol.
  • Top-1 per Game Variations within Expected Variance. Port top configurations (aug_noise global, contrastive starpilot, resnet18 bossfight) differ from SB3 study top models (mlp_vector, mlp, aug_crop), but all pairwise comparisons show d ≤ 0.4 with overlapping CIs — reinforcing section 3.8: with 5 seeds, top-spot swaps reflect sample variance rather than methodological divergence.
  • Absolute Discrepancy Disentanglement. Controlled isolation experiments showed: (1) JAX with SB3-sized batches (4 envs, minibatch 64) produced identical returns to large-batch JAX; (2) SB3 scaled to large batches (32 envs, minibatch 1024) increased maze performance to 5.33 (vs 2.80 in single-env SB3). Conclusion: Discrepancies stem from parallel rollout batch dynamics, not framework differences. Documented in paired_sb3_big_maze.json.
  • Environment Reproducibility. Pinned via jax_port/requirements-jax.txt and documented in jax_port/SETUP_VENV.md.
  • Budget Scaling (§3.13): mlp exhibits modest gains (2.04→2.74 in starpilot), while resnet18 remains flat — confirming that expanded budgets do not alter architectural conclusions.
  • Top Cluster Expanded Evaluation (n=10 Seeds): Sample size doubled across top 5 configurations (seeds 42–51, full evaluation): bossfight impoola 0.20 > mlp 0.09 (d=0.73, overlapping CIs); starpilot aug_noise 2.63 ≈ impoola 2.61 (d=0.06); dodgeball impoola 1.29 ≈ mlp 1.28 (d=0.05). Verdict: Even with n=10, the top cluster remains statistically inseparable — reinforcing section 3.8.
  • Extensions (No Baseline in Original Study): gat achieves ~1.3 global return; spr_aug > spr; acl > curl/cpc across all 3 games; hard difficulty suppresses all returns to ~0.05.
  • Generalization Gap Re-Ranking (gen_gap = eval_train − eval_unseen): Sorting by minimal generalization gap preserves relative order — top models: aug_noise (−0.47), lstm_attention (−0.45), resnet18 (−0.44), contrastive (−0.41), with overlapping CIs throughout (analysis_full.json). High return correlates with strong generalization; models do not succeed via training-set memorization.

15.4.2. Sample Budget Viability: 50k vs 100k Steps (Probe 05/09/2026, Classic/MLP × Coinrun/Starpilot × Seeds 42–43, Eval 30/30/15)

Game Extractor 50k (Eval Unseen) 100k (Eval Unseen) Δ
coinrun classic 4.50 5.00 +0.50
coinrun mlp 2.50 3.00 +0.50
starpilot classic 2.02 3.05 +1.03
starpilot mlp 1.93 2.53 +0.60

Verdict: 50k steps preserves relative ranking order (classic > mlp across all 4 pairs), and deltas (+0.5–1.0) are smaller than seed-to-seed variance. Combined with §3.13 (stagnation from 100k→500k), the conclusion is: 50k steps suffices for relative ranking (the study's objective), while 100k provides higher absolute scores and safety margin. The full grid runs at 100k steps.

15.4.3. Measured Runtime Reductions (Identical RTX 4070 Laptop)

  • coinrun 50k, 1 model: SB3 ~7 min (§2) → JAX ~8 s training (~50×).
  • 100k cell: SB3 ~10 min → JAX ~14 s training + ~40 s full evaluation ≈ 1 min end-to-end (~10×).
  • 15 h SB3 run (~8M steps): → ~18 min at 7.5k SPS.
  • Complete grid (615 cells, 10 suites × 5 seeds, full evaluation): SB3 estimated at multiple weeks → JAX completed in ~7–8 h end-to-end (completed 06/09, master_full.json 615/615 verified; serialized in analysis_full.json). Cumulative measured training time: 5.0 h vs ~90–100 h estimated in original study → ~18× in training, ~12× end-to-end with comprehensive evaluation.
  • SPR Extension: spr.py — shared encoder + target EMA (τ=0.99) + transition MLP, MSE loss on normalized latents with crop augmentation, auxiliary Adam lr 1e-4. Smoke test verified.
  • GAT Extension: GATPatch — graph over 64 8×8 patches, 2× GAT with 4 heads + residual/LayerNorm, mean-pool → FC512 (141k params). Smoke test on coinrun: return 6.0, verified.
  • CURL/CPC/ACL Extensions: contrast.py — shared encoder + target EMA, InfoNCE τ=0.1 with minibatch negatives. Resolved gradient singularity on exact zero vectors via epsilon regularization sqrt(sum + eps).
  • MARL/SMAX Integration (jaxmarl 0.1.0): jax_port/marl/ — 7 algorithms (ippo, mappo, vdn, qmix, mapoca, cte, tarmac) on map 3m × 3 seeds × 1M steps (21/21 runs, 1.5–3.3k SPS). Outcome: Win-rate evaluated at 0.0 across all flat feedforward implementations (analysis_full.json) — flat feedforward policies fail to coordinate in 3m within 1M steps. Diagnosed that JaxMARL literature requires recurrent GRU-128 + 10M steps, implemented in recurrent.py with sequential BPTT over window T.
  • Exploration $\beta=0$ Control Suite (jax_port/exploration_control_summary.json): Evaluated all 30 control cells (3 arms × 2 games × 5 seeds at 100k steps with --explore-beta 0.0 where the bonus network trains but nothing is injected into the reward). Outcome: the chaotic drift floor ($|arm@0 - ppo|$) exceeds the published delta ($|arm@0.01 - ppo|$) across all 6 groups (heist_icm: drift 0.22 vs effect -0.18; heist_rnd: drift 0.38 vs effect -0.18; maze_ngu: drift 2.50 vs effect -0.58). Scientific Verdict: The published $\pm 0.5$ exploration spread in the JAX port is entirely within chaotic run-to-run drift and not attributable to intrinsic curiosity mechanisms.

15.4.4. Recurrent MARL at 10M Steps with GRU-128: Empirical Evaluation (06/09/2026)

Evaluated protocol: IPPO-rec and QMIX-rec on map 3m, seeds 42–44, 10M steps each (~1h15 for IPPO at ~2.1k SPS; ~1h55 for QMIX at ~1.4k SPS per run). All 6 runs converged to 0.0 evaluation win-rate across all seeds.

Algorithm Mean Training Return Eval Win-Rate (32 eps) Eval Return
IPPO-rec (3 seeds) 1.6–1.9 0.0 1.6–1.9
QMIX-rec (3 seeds) ~0–0.3 0.0 0.0–0.3

Ruled-out Failure Modes: Pipeline correctness verified (hidden state carry, agent-specific done masking, sequence slicing). Extended budget (10M) and map configuration adjustments (--no-walls) yielded identical 0.0 win-rates across seeds.

Empirical Diagnosis: IPPO-rec acquires partial credit (eliminating 1–2 opposing units before defeat — stable return ~1.7), while QMIX acquires minimal credit (~0). Symmetric 3v3 combat rewards strict focus-firing and damage avoidance; partial-credit gradients form a local optimum. Discrepancies relative to JaxMARL baseline literature stem from rollout batch composition (upstream uses 8 parallel envs with 4 epochs/rollout, vs 32 envs with 2 epochs here) and full-episode TD windows vs length-32 sequence chunks.

Positive Control on 2s3z (09/09, QMIX-rec 10M × 3 seeds): Tested on 2s3z (the standard JaxMARL benchmark map). Outcome: 0.0 win-rate across all 3 seeds (returns 0.0–0.2). This confirms that the behavior is governed by specific off-policy Q-learning hyperparameter recipes rather than environment mechanics or hidden state handling. SMAX investigation concluded without further resource allocation. Checkpoint restoration functionality implemented under --ckpt/--resume. (Status 27/09/2026: these three cells predated the 21/09 fixes by twelve days, so this sentence was originally an inference from a run whose mixer target never saw the external reward. They have since been re-measured under the corrected code with identical hyperparameters and the outcome did not move — see the re-measurement block below.)

Algorithmic Root Cause Resolution (21/09/2026): A deep algorithmic audit of jax_port/marl/recurrent.py identified a critical deficiency in make_ql_seq_update: the mixer target computation omitted the environment reward and discount terms during sequence Bellman target construction:

# Previous buggy formulation:
ttot = mixer.apply(target_mixer_params, target_qs, states)
# Mathematically corrected Bellman target:
target_tot = rew + gamma * (1.0 - done) * mixer.apply(target_mixer_params, target_qs, next_states)

Without scaling by $r + \gamma(1-d) Q_{tot}^{target}$, the mixer loss $L = (Q_{tot} - target)^2$ was deprived of the external reward signal. In addition to repairing the Bellman target equation, we integrated Polyak soft target updates (--tau) and configurable update frequencies (--target-update-interval) into jax_port/marl/train_ql.py, verified via unit tests in jax_port/tests/test_marl.py (MARL_TESTS_OK). Correction (23/09/2026): this paragraph previously stated a --tau default of 0.005; the actual default is tau = 0.0, which selects the hard target copy every 500 gradient steps, and no published MARL cell passed --tau — so every MARL result in section 15.4.4 was produced with hard target updates. run_grade.py now exposes both flags so the grid can reach them.

Definitive Empirical Re-Measurement under Corrected Code (24/09/2026, jax_port/marl_remeasure_summary.json):
To definitively resolve whether the 0.0 win-rate was caused by the code defects or fundamental optimization dynamics, all 9 Q-learning cells were re-measured from scratch via jax_port/marl_ql_remeasure.sh on map 3m under the mathematically corrected codebase (verified contiguous sequential buffer, terminal masking, and target mixer scaling):

  • vdn flat (1M steps × seeds 42–44): 0.000 eval win-rate (returns 0.50–0.56)
  • qmix flat (1M steps × seeds 42–44): 0.000 eval win-rate (returns 0.00–0.49)
  • qmix-recurrent (10M steps × seeds 42–44): 0.000 eval win-rate (returns 0.00)

The re-measurement was incomplete; it is now (27/09/2026, jax_port/marl_remeasure_2s3z_summary.json): the 24/09 sweep paid for 9 cells, but the published grid carries 12 invalidated Q-learning cells — the qmix-recurrent 10M positive control on 2s3z above ran on 09/09 under all three defects, and its 0.0 win-rate was the evidence for the claim that the failure is about hyperparameter recipes rather than map mechanics or hidden-state handling. Those 3 cells were re-measured from scratch under the corrected code with otherwise identical settings (MAPS=2s3z RUN_FLAT=0, so no unpublished flat 2s3z cells were invented alongside them; no --tau, hard target copy every 500 gradient steps), into their own directory and their own summary so the frozen 3m numbers stay untouched: 10M steps × seeds 42–44 → 0.000 eval win-rate (returns 0.00, 0.00, 0.12), all three cells reaching their full budget and marked usable, delta_vs_published = 0.000 on each, 6.9 h wall (10602 s / 7416 s / 6948 s, at 943–1439 SPS).

🔬 Definitive Scientific Verdict: Re-measurement confirms that the 0.0 win-rate in SMAX symmetric 3v3 combat is not an artifact of the mixer target omission or buffer bugs — and that now holds on both maps, 3m (9 cells) and 2s3z (3 cells), i.e. every Q-learning cell the study publishes was produced by code whose Bellman target actually sees $r + \gamma(1-d)Q_{tot}^{\text{target}}$. Rather, off-policy cooperative Q-learning without domain-specific credit assignment shaping or dense reward curricula fails to converge to coordinated focus-firing in either map. This settles the methodological integrity of the SMAX benchmark.

15.4.5. Temporal Architecture Bake-Off — 10 Memory Models, 100k Steps, 5 Seeds (06–07/09/2026, Suite temporal)

Research question: In ProcGen with frame stack $k=4$, does explicit recurrent memory yield measurable advantage? Evaluated on heist, maze, and jumper. Full suite completed at 150/150 runs (master_temporal.json):

Game 1st 2nd 3rd Lowest Distribution
heist s4 3.10 cnn1d 2.78 s5 2.76 transformer_xl 1.42 Top-7 show overlapping 95% CIs
jumper s5 3.56 mlp 3.54 mamba 3.44 transformer 2.72 Top-4 within 0.12 of each other
maze cnn1d 5.14 tcn 5.06 lstm 4.94 transformer 3.94 Top-6 within ~0.4 of each other

Findings: (1) No recurrent memory architecture separates from simple 1D temporal convolution or stateless MLP within 100k steps — replicating section 3.8: differences reflect sample variance rather than systematic superiority. (2) Consistent underperformers: transformer and transformer_xl — higher parameter counts (~1M) with self-attention struggle under a 100k step budget. (3) State space models (s4/s5/mamba) perform competitively without dominating feedforward baselines.

15.4.6. Memory Models under Extended Budget (500k Steps) on Hard Regimes (07/09/2026, Suite temporal_hard)

Evaluated the same 10 architectures on heist (hard mode) and bossfight across 500k steps × 5 seeds with full evaluation (100/100 runs completed):

heist hard Mean Return bossfight hard Mean Return
s5 2.02 s5 0.20
tcn 1.98 gru 0.17
s4 1.92 s4 0.15
gru 1.70 cnn1d 0.14
mlp 1.66 tcn 0.13
cnn1d 1.60 mamba 0.07
lstm 1.56 lstm 0.05
mamba 1.50 mlp 0.05
transformer 1.50 transformer 0.03
transformer_xl 1.46 transformer_xl 0.01

Analysis:

  1. Transformer models do not scale with budget: Relative to the 100k baseline, transformer remains at the bottom tier in both environments. The hypothesis that "Transformers merely lacked budget" is rejected up to 500k steps.
  2. State space models (s5, s4, mamba) lead in hard heist, consistent with 100k trends, though confidence intervals overlap with top convolutional baselines.
  3. bossfight suppresses all architectures (0.0–0.2), offering no meaningful temporal differentiation.
  4. Conclusion: Explicit temporal memory does not provide significant advantage over feedforward baselines in standard Procgen environments under 100k or 500k budgets.

15.4.7. Single-Frame Regime (stack=1): Audit and Paired Recurrent Memory Benchmark (20/09/2026, starpilot, 100k Steps, Seed 42)

A key methodological critique of the preceding bake-offs (§15.4.5 and §15.4.6) is their reliance on stack=4. Stacking 4 frames provides a first-order Markovian approximation (velocity via finite differences), potentially masking the necessity of explicit recurrent memory.

Pre-Experiment Infrastructure Audit: Auditing temporal.py and train.py revealed that early temporal models (LSTMStack, S5Stack, etc.) operated recurrence strictly within the 4 stacked frames, remaining stateless across environment transition steps ($t \to t+1$, resetting hidden state carry per observation). Executing those implementations with stack=1 would reduce them to sequence length 1 (effectively feedforward linear layers).

To rigorously evaluate memory under strict partial observability (POMDP) without frame stacking, true inter-step recurrence was implemented (jax_port/recurrent_step.py: RecurrentLSTMBackbone and RecurrentS5Backbone), with hidden state carries maintained across rollouts and reset strictly on done, evaluated via jax_port/bench_temporal_stack1.py on starpilot (where $k=1$ removes direct velocity observation):

Model Architecture SPS Wall (s) Train (Last 20 eps) Eval Unseen (20 eps) 95% CI Source
classic NatureCNN feedforward (stateless, $k=1$) 7,566 14.1 s 2.65 3.10 [1.73, 4.47] results_stack1_bench_100k.json
recurrent_s5 ClassicCNN + S5 SSM step-carry ($k=1$) 5,088 20.9 s 2.25 2.20 [1.29, 3.11] results_stack1_bench_100k.json
recurrent_lstm ClassicCNN + LSTM cell step-carry ($k=1$) 4,982 21.4 s 3.20 1.45 [0.64, 2.26] results_stack1_bench_100k.json

Scientific Findings:

  1. Simple feedforward policy (classic) superior in generalization: Even under forced partial observability ($k=1$), stateless NatureCNN achieved the highest return on unseen levels (3.10), outperforming both recurrent architectures.
  2. Latent State Overfitting in LSTM: recurrent_lstm attained the highest training return (3.20) but collapsed to 1.45 on unseen levels (generalization gap of −1.75). In small budgets (100k), recurrent capacity primarily memorized training trajectories across the 200 levels.
  3. Stability of State Space Models (S5): recurrent_s5 exhibited near-zero generalization gap (2.25 train $\to$ 2.20 unseen eval), though it did not outperform the feedforward baseline.
  4. Computational Throughput: Step-by-step recurrent state tracking incurred a ~33–34% throughput penalty (~7.5k $\to$ ~5.0k SPS).
  5. Final Verdict: Even without frame stacking, explicit sequential memory does not offset optimization complexity in the 100k step Procgen regime; feedforward policies remain more sample-robust.

Architectural Feasibility Taxonomy of 10 Models at stack=1:

Category Model at stack=4 Mechanism at $k=4$ Structural Behavior at $k=1$ Status at stack=1
Convolutional & Stateless mlp (MlpStack) MLP over concatenated frames Degenerates to standard MLP over 1 frame. No memory. Redundant with classic.
cnn1d 1D temporal conv over 4 frames At $T=1$, 1D conv degenerates to dense linear layer. No memory. Requires explicit FIFO frame buffer.
tcn Causal dilated conv over 4 frames Causal conv at $T=1$ degenerates to linear projection. No memory. Requires explicit FIFO frame buffer.
Pure Attention transformer Self-attention across 4 frames 1-token self-attention is trivial identity ($QK^T = 1$). Degenerates to feedforward. Inapplicable without sequence length.
transformer_xl Self-attention with segment cache Could maintain FIFO cache of latents, but requires 1-frame input adaptation. Adaptable via step-by-step cache.
Recurrent & SSMs ($h_t = f(h_{t-1}, x_t)$) lstm LSTMCell over 4 frames Requires $(c_t, h_t)$ carry across environment steps, reset on done. Implemented (recurrent_lstm).
s5 Associative scan over 4 frames Scan reduces to recursive step $h_t = \bar{A}h_{t-1} + Bu_t$. Implemented (recurrent_s5).
gru GRUCell over 4 frames Requires carry $h_t$ across environment steps, reset on done. Viable (direct extension of LSTM).
s4 FFT convolution over 4 frames Step-by-step operates as discrete SSM. Viable (SSM variant).
mamba Selective scan over 4 frames Operates as input-dependent recurrent step. Viable (time-varying SSM).

Engineering Conclusion: Temporal convolutions (cnn1d, tcn) possess no latent state and require an external observation buffer (which merely reconstructs frame stacking). Only models with recurrent latent state equations ($h_t = f(h_{t-1}, x_t)$) constitute valid memory architectures at $k=1$. recurrent_lstm and recurrent_s5 represent the gated recurrent and state-space families, respectively.

15.4.8. Single-Frame POMDP Occlusion Benchmark on Caveflyer & Memory Regularization (21/09/2026)

Following the finding in §15.4.7 that feedforward policies outperformed recurrent models in starpilot (where velocity is latent but all entities remain on-screen), we conducted a rigorous follow-up addressing two key methodological factors:

  1. Memory State Transition Alignment: Auditing the rollout buffer in jax_port/bench_temporal_stack1.py revealed that memory carries were saved after stepping the environment (b_mem[t] = next_mems). For correct recurrent policy gradient backpropagation through time (BPTT), the pre-transition carry $h_{t-1}$ must be stored at step $t$ (b_mem[t] = mems), aligning the state that conditioned action $a_t$ with observation $s_t$.
  2. Hidden Carry Regularization (RegularizedRecurrentLSTMBackbone): To prevent LSTM hidden state drift and memorization overfitting under low-data budgets, we implemented RegularizedRecurrentLSTMBackbone in jax_port/recurrent_step.py, introducing LayerNorm on the recurrent hidden carry $(h_t, c_t)$ along with AdamW weight decay ($1\times 10^{-4}$).
  3. Authentic Spatial Occlusion Benchmark (caveflyer): Unlike starpilot where all adversaries are visible in the viewport, caveflyer features winding subterranean caverns and occluded target goals outside the camera frame — constituting an authentic Partial Observability (POMDP) environment where path memory is structurally required.

We benchmarked classic (feedforward NatureCNN), recurrent_lstm, regularized_recurrent_lstm, and recurrent_s5 under stack=1, 57k steps, across a 5-seed evaluation ($n=5$, seeds 42–46) with 20 episodes on unseen levels per seed (seed+1000), persisted in results/caveflyer_multiseed_bench.json:

Model Architecture Mean Return ± Std 95% Student's $t$ CI Cohen's $d$ vs Classic Per-Seed Breakdown (42–46) Source
recurrent_lstm CNN + LSTM pre-transition carry ($k=1$) 3.90 ± 1.14 [2.48, 5.32] +0.57 (medium-to-large) [2.5, 5.0, 4.0, 3.0, 5.0] caveflyer_multiseed_bench.json
recurrent_s5 CNN + S5 SSM step-carry ($k=1$) 3.60 ± 1.14 [2.19, 5.01] +0.33 (small-to-medium) [2.5, 3.5, 5.5, 3.0, 3.5] caveflyer_multiseed_bench.json
classic NatureCNN feedforward (stateless, $k=1$) 3.20 ± 1.30 [1.58, 4.82] — (baseline) [1.0, 4.0, 4.0, 3.0, 4.0] caveflyer_multiseed_bench.json
regularized_recurrent_lstm CNN + LayerNorm carry + AdamW ($k=1$) 3.20 ± 0.91 [2.07, 4.33] +0.00 (neutral) [3.0, 4.5, 3.0, 2.0, 3.5] caveflyer_multiseed_bench.json

Scientific Conclusion (Definitive Multi-Seed Re-measurement): Across $n=5$ seeds, recurrent_lstm retains the highest mean unseen return ($3.90$ vs $3.20$, $+22%$ relative advantage) with a positive effect size ($d = +0.57$). However, due to high inter-level variance in procedural generation, the 95% Student's $t$ confidence intervals overlap ($[2.48, 5.32]$ vs $[1.58, 4.82]$). This contextualizes the exploratory single-seed pilot: the single-seed point estimate ($4.00$ vs $2.50$, $+60%$) substantially exaggerated the true advantage. While temporal state-carry provides a consistent, measurable directional benefit in occluded POMDP environments ($d=+0.57$), procedural variance necessitates multi-seed aggregation to prevent over-claiming statistical separation. S5 SSMs also achieve a positive effect size ($d=+0.33$), whereas hidden-state LayerNorm regularization (regularized_recurrent_lstm) reduces return variance ($\pm 0.91$ vs $\pm 1.14$) at the expense of peak return.

15.3. Controlled Benchmark Comparison — Identical System, Same Day (05/09/2026, coinrun, 100k Steps, Seed 42)

Benchmarking conditions: Training loop wall-clock time isolated (excluding environment initialization, evaluation callbacks, and disk serialization); identical PPO hyperparameters (lr 3e-4, n_steps 256, 3 epochs, γ 0.99, λ 0.95, clip 0.2); sequential execution on the same RTX 4070 Laptop. Configurations: A SB3 matching original study (DummyVecEnv n=1, batch 64, bench_sb3_paired.py); B Parallel SB3 (SubprocVecEnv n=64, batch 1024); C JAX (jax_port/train.py, 64 envs gym3, batch 1024).

Arm Steps Training Wall-Clock SPS Source
A: SB3 n=1 / batch 64 (Original Study) 100,096 175.5 s 570 jax_port/paired_sb3_n1.json
B: SB3 n=64 / batch 1024 114,688 16.8 s 6,838 jax_port/paired_sb3_n64.json
C: JAX n=64 / batch 1024 106,496 14.2 s 7,506 jax_port/pa2_coinrun_100k_rerun.json

Throughput Decomposition (~30–70× vs ~120–170 SPS reported): (1) ~4× stems from removing logging/callback overhead present during full benchmark runs (570 paired vs ~120–170 reported in config A). (2) ~12× is driven by environment parallelism — transitioning A→B (570→6,838), attainable in either framework. (3) ~1.1× represents framework and environment bindings — comparing B→C (6,838→7,506), PyTorch/SubprocVecEnv vs JAX/gym3 C++ bindings. While JAX is ~15× faster than the original single-env study configuration under controlled conditions (7,506 vs 570), it is only ~10% faster than an equally parallelized SB3 setup — demonstrating that batch parallelization accounts for the vast majority of throughput gains.

15.2. PA2-Speed — Active PPO Training at 7.0–8.6k SPS (04/09/2026, /root/procgen-jax, WSL2, RTX 4070 Laptop)

Learner components: jax_port/networks.py (NatureCNN matching models/sb3_extractors.py:8, NHWC layout), jax_port/ppo.py (clipped surrogate objective, value clipping, entropy bonus; lr 3e-4, γ 0.99, λ 0.95, clip 0.2, 3 epochs, vf 0.5, ent 0.01, grad clip 0.5, adv norm — matching study hyperparameters in compare_suite.py:26), jax_port/train.py (64 C++ gym3 environments, rollout 128, minibatch 1024).

wsl -e env PYTHONPATH=/mnt/c/Users/Acer/Downloads/MLE \
  /root/procgen-jax/bin/python \
  /mnt/c/Users/Acer/Downloads/MLE/jax_port/train.py \
  --game coinrun --timesteps 100000 --seed 42 --num-envs 64
Game Steps Training Wall-Clock Training SPS Train Return Eval Unseen (10 eps) SB3 Baseline
coinrun seed 42 106,496 12.3 s 8,647 7.50 3.0 ~120–300 (≈7 min/50k, §2) → ~30–70×
starpilot seed 42 106,496 14.6 s 7,281 2.05 1.9 ~167 (≈10 min/100k, §1.4) → ~40×

Sources: jax_port/pa2_coinrun_100k.json, jax_port/pa2_starpilot_100k.json.

Empirical Interpretation: (1) The initial 3–5k SPS target was exceeded during full gradient updates — executing 100k steps in ~12–15 s vs ~7–10 min in single-env SB3. (2) Controlled comparison against equally parallelized SB3 is documented in §15.3, attributing ~1.1× to framework execution. (3) One-time startup costs: XLA compilation and kernel autotuning (~3.5 min, cached under /tmp/jax_port_cache) + GPU context allocation (~4 s). (4) Returns demonstrate active policy convergence. (5) Host-side minibatch slicing avoids device gather latency (~40× faster).

Parity Considerations. Acceptance criteria for the JAX port: reproduce ranking orders and statistical error bounds within inter-seed variance, rather than maximizing throughput in isolation.

Artifact Organization. Port implementation resides within jax_port/: core modules (vector_env.py, bench_throughput.py, networks.py, backbones.py, ppo.py, dqn.py, augment.py, exploration.py), trainers (train.py, train_dqn.py, train_hrl.py), verification suite (stats.py, run_grade.py, tests/), controlled benchmark scripts (bench_sb3_paired.py at root + paired_*.json), and empirical outputs (pa1_*, pa2_*). Root study files (models/, results/) remain intact.


16. Process References (Beyond Section 10)

  • Gate PA0 and Environment: PPA deadsnakes (Python 3.10 on Ubuntu 24.04); wheel procgen-0.10.7-cp310-cp310-manylinux; jax[cuda12]; pins: numpy==1.26.4, gym==0.26.2, flax==0.10.7, optax==0.2.8, JAX==0.6.2.
  • Gym/Gymnasium API Boundaries: procgen_wrapper.py:6,55,85 (original study) serving as the baseline interface specification.
  • GPU Workload Discipline: Single active process on 8 GB VRAM; graceful termination with serialized state checkpoints (established in compare_suite_retrain.py).
  • Reference Hardware Environment: WSL2 Ubuntu 24.04, NVIDIA RTX 4070 Laptop 8 GB, cuda:0, Python 3.10.11 (original study) / dedicated py3.10 venv (JAX port).

17. Multi-Agent RL Architecture & Sequential Buffer Alignment (jax_port/marl/)

The multi-agent module (jax_port/marl/) provides cooperative and competitive multi-agent learning architectures implemented in JAX/Flax.

17.1. Temporal Causality and Buffer Partitioning

In recurrent multi-agent Q-learning, agents update their parameters through Truncated Backpropagation Through Time (TBPTT) over trajectories of length $L$.

  • Legacy Artifact: Circular buffers indexed linearly across parallel environments mix observations from disparate environments ($e_0, e_1, \dots, e_{N-1}$), violating temporal causality.
  • MARLSequentialBuffer: Contiguous histories are allocated and stored per parallel environment independently: $$\text{Obs Buffer Shape}: (N_{\text{envs}}, C_{\text{env}}, \dots)$$ Sampling selects an environment index $e \sim \mathcal{U}(0, N_{\text{envs}}-1)$ and a valid temporal window $[t, t+L]$ within that specific environment's ring buffer, guaranteeing that sampled sequences represent genuine, contiguous Markovian trajectories ($s_t^e, a_t^e, r_t^e, s_{t+1}^e$).

17.2. Bellman Target Terminal Masking

The recurrent Q-learning Bellman target is given by: $$y_t = r_t + \gamma \max_{a'} Q(s_{t+1}, a'; \theta^-) \cdot (1 - d_t)$$ In jax_port/marl/train_ql.py, the terminal array done is converted to a JAX array (jnp.asarray(bd)) and passed into make_ql_seq_update, strictly preventing bootstrapped value propagation across episode terminations.


18. RLiable Evaluation Protocol & Canonical Benchmark Normalization

To eliminate evaluation pathologies (such as outliers skewing the arithmetic mean, or uninformative point estimates lacking confidence intervals), the benchmark suite incorporates the evaluation methodology developed by Agarwal et al. (NeurIPS 2021) via rliable_metrics.py and run_rliable_eval.py. Those two files are a self-contained implementation on top of NumPy — the rliable package is not a dependency of this repository and is not pinned in requirements.txt.

18.1. Empirical Random Baselines across Procgen

Rather than assuming artificial minimum bounds, uniform random policies were empirically evaluated across 50 episodes per game on unseen levels (num_levels=0, distribution_mode='easy', evaluation seed 1042 — the same protocol used to evaluate trained models). The measurements are produced by random_baselines.py (added 23/09/2026; previously the JSON existed with no script able to regenerate it) and can be reproduced with py -3.10 random_baselines.py --episodes 50:

  • bossfight: $0.00 \pm 0.00$
  • starpilot: $1.78 \pm 1.82$
  • dodgeball: $0.96 \pm 1.35$
  • maze: $4.40 \pm 5.01$
  • heist: $2.80 \pm 4.54$

Per-episode returns and the protocol record are serialized in results/random_baselines.json. Standard deviations are sample standard deviations ($ddof=1$).

⚠️ Consequence for maze and heist (must be read together with section 3.12): under this protocol a uniform random policy solves maze in 22 of 50 episodes and heist in 14 of 50, while every trained arm on those two games lands at maze $2.76$–$2.80$ and heist $0.72$ — i.e. below the random point estimate. Taking the standard errors of the anchors ($4.40 \pm 0.71$ S.E. for maze, $2.80 \pm 0.64$ S.E. for heist, $n=50$), heist is clearly worse than random (95% CI $[1.54, 4.06]$ does not reach $0.72$) whereas maze is borderline (95% CI $[2.97, 5.83]$ just overlaps $2.80$). Procgen maze/heist in easy mode use small levels with a 600-step budget, so a random walk reaches the goal often enough to score $9$–$10$ more frequently than these policies. Two conclusions follow: (i) the maze/heist rows of the section 3.6 and 3.12 tables must not be read as "ICM beats PPO" — all four arms are within $0.04$ of each other and at or below the random anchor; (ii) these two games are excluded from canonical normalization (section 3.14 item 4). The ppo/icm/rnd/ngu arms are also where the broken-wrapper defect of section 3.6 was found: the exact ppo=rnd=ngu equality across maze and heist was not a null result but a swallowed RuntimeError, now fixed and covered by tests/test_exploration_bonuses.py.

18.2. Canonical Normalization Formula

Per Agarwal et al., game returns are normalized against empirical random baselines and maximum task performance: $$\bar{z}{m, g} = \frac{R{m, g} - R_{\text{random}, g}}{R_{\text{expert}, g} - R_{\text{random}, g}}$$ Both Canonical Agarwal normalization and empirical min-max spans are computed and recorded in results/rliable_scorecard.json.

18.3. Scorecard and Statistical Profiles

The consolidated scorecard across top architectures (results/rliable_profile.png) presents three complementary panels:

  1. Aggregated Interquartile Mean (IQM): Computed across the middle 50% of normalized scores, averaged over the three suite games, with 95% Stratified Bootstrap Confidence Intervals ($B = 10{,}000$ replicates, taken from rliable_metrics.NUM_BOOTSTRAPS).
  2. Empirical Performance Profiles with Shaded Bootstrap Bands: Cumulative distribution functions $\hat{F}(\tau)$ plotted with pointwise 95% bootstrap confidence bands (fill_between), showing the fraction of runs exceeding threshold $\tau$ (same $B = 10{,}000$).
  3. Multi-Metric Forest Plot: Comparative visualization of IQM, 5% Trimmed Mean, Median, and Arithmetic Mean over the top 5 architectures.
  4. Canonical normalization scope: results/rliable_scorecard.json now records canonical_games and canonical_exclusions with an explicit reason per excluded game, instead of silently substituting a placeholder reference mean.

19. Architecture Verification & Test Suites

The repository contains automated unit tests across both PyTorch and JAX backends:

  • PyTorch Extractor Test Suite (tests/test_pytorch_extractors.py):

    • Validates forward pass output dimensions $(B, 512)$ for all 12 extractor configurations (ClassicCNNExtractor, AttentionCNNExtractor with CBAM / Spatial, ImpalaCNNExtractor, ImpoolaCNNExtractor, ResNet18Extractor, LSTMAttentionExtractor, ViTExtractor, VAEExtractor, AEExtractor, ReconExtractor, ContrastiveExtractor).
    • Verifies autograd backward pass and ensures non-zero parameter gradients.
    • Tests compatibility across Procgen HWC $(64, 64, 3)$ and standard CHW $(3, 64, 64)$ observation formats, that features_dim is actually honoured, and that a non-contiguous channels-first view yields the same features as a contiguous copy.
    • Previously this module fell back to a DummyPytest shim when pytest was absent, which turned every @pytest.mark.parametrize into a no-op and reported green without running the cases; the shim is gone and pytest is pinned in requirements.txt.
  • SB3 Integration Tests (tests/test_sb3_integration.py): build a PPO("CnnPolicy", ...) on each representative extractor and call predict(), over both a CHW space (what ProcgenGymWrapper yields) and an HWC space. This caught a real defect: every extractor decided its tensor layout from the observation space, so under SB3 — which already permutes HWC to CHW in preprocess_obs — a channels-last environment was permuted twice and crashed in the first convolution, despite the class docstrings advertising HWC support. Layout is now inferred from the tensor (models/layout.py), and the CHW path used by every published number is unchanged.

  • Statistical Aggregation Tests (tests/test_rliable_metrics.py): pin the per-game-then-average aggregation semantics, the single bootstrap constant, profile monotonicity and probability-of-improvement antisymmetry.

  • Legacy Data Provenance Tests (tests/test_legacy_records.py): assert that results/legacy_records.json still reproduces the published mean/std/ci95 of section 3.8 and the section 3.7 ranking used by retrain_analysis.py.

  • World-Model Extractor Tests (tests/test_world_model_extractors.py): dream() must return the input's spatial resolution, must share one encoder with forward(), and must react to encoder weights; the augmentation hook is only applied in training mode.

  • Intrinsic Reward Tests (tests/test_exploration_bonuses.py): every bonus wrapper must add a bonus on every step, accept HWC and CHW observations, raise on a malformed observation instead of degrading to plain PPO, keep the RND target frozen, and scale linearly with beta.

  • Grid Analysis Tests (tests/test_grid_analysis.py): jax_port/cells_summary.json must still regenerate the published analysis_full.json (every cell, ranking and metric), which is what makes the committed summary an adequate stand-in for the git-ignored raw grid logs.

  • Figure Provenance Tests (tests/test_report_figures.py): the set of figures embedded in this README must equal the PROVENANCE map, each entry must name a script that still exists and a real data source, and the figures claimed rebuildable must have their per-seed arrays present (5 seeds each) in results/legacy_records.json.

  • Symbolic Regression Tests (tests/test_symbolic_regression.py): eleven cases guarding section 21. The important one is not about search quality but about validity: it perturbs every measured return and asserts the feature matrix is bit-identical afterwards, which is what proves gen_gap (train − unseen) never leaked into the inputs. Also covered: an unmapped configuration raises instead of being silently dropped, Wilson intervals behave and widen with fewer trials, the complexity-vs-error and inversion helpers, and a real gplearn fit — the guard that would have caught the scikit-learn 1.7 incompatibility at CI time rather than twenty minutes into a study run.

  • JAX Port Test Suite (python -m jax_port.tests.run_tests) — six registered modules, each SKIP-ponly on a missing optional dependency:

    • test_stats: Student-t CI, Cohen's d, AUC normalization and the top-1-vs-top-2 CI-overlap predicate (both the separated and the indistinguishable case).
    • test_parity: old-gym Procgen vs the JAX env on observations, rewards and autoreset over 200 steps.
    • test_zoo: all 13 backbones forward-pass with shape and parameter-count checks.
    • test_smoke: end-to-end PPO training run; its arguments are built by train.build_parser() so a flag rename cannot leave a stale hand-built namespace behind.
    • test_temporal: memory-backbone shapes and StackVec stacking — previously orphaned, never invoked by the runner.
    • test_marl: run_all() executes all five cases — MARLSequentialBuffer temporal integrity and per-environment isolation, recurrent Q-learning Bellman target masking and gradient updates, battle metrics and win-rate accounting, adapter autoreset, and the finite-loss sweep over the seven algorithms. Before 23/09/2026 the runner registered only test_losses, while this section cited the other four as validation evidence.

20. Legacy Continuous Control Prototypes (legacy/carracing/, removed 27/09/2026)

The early Phase 1 exploratory codebase developed prior to the project's transition to discrete-action Procgen benchmarks was dropped from the working tree on 27/09/2026 as out of scope for this study. It is not lost: every file below is readable from any commit up to 16916f2 (git show 16916f2:legacy/carracing/sac_trainer.py), which is where the removal records it from.

  • legacy/carracing/sac_trainer.py: Custom continuous Soft Actor-Critic (SAC) implementation with Box action space.
  • legacy/carracing/compare_architectures.py: Training harness benchmarking Classic CNN vs CBAM CNN on CarRacing-v2 / CarRacing-v3.
  • legacy/carracing/README.md: Architectural motivation and historical context for the transition to Procgen.

Unrelated to the above, and still committed: results/legacy_records.json holds the per-seed arrays of the pre-rliable study and is study data, not prototype code — the name collision is unfortunate but the two must not be confused during cleanups.


21. Symbolic Regression as System Identification of the Benchmark (symbolic_regression.py)

Sections 3 and 15 report what was measured. This section asks the inverse question: is there a short closed-form law over the protocol variables that reproduces those measurements, and can it be used backwards — to recover the hidden configuration from an observed return, or to infer the budget a target return implies?

That is a system-identification problem in the standard sense: the data are outputs $R_{i,j}$ of an unknown generator $f(\text{protocol}) + \varepsilon$ observed at configurations $i$ and seeds $j$, and the object of interest is $f$ itself rather than a prediction. The engine is gplearn 0.4.2 genetic symbolic regression; the inputs are the 825 executed cells already committed in jax_port/cells_summary.json. Nothing in this section is simulated — every row is a measurement the grid produced (§15.4).

21.1. Data, Support and Leakage Controls

Control Value
Rows 825 cells (of 865 non-MARL; the 40 hrl cells are excluded because they were logged without a budget)
Games with budget variation bossfight (235), starpilot (170), dodgeball (160), heist (120)
Games without any budget variation coinrun (20), maze (70), jumper (50) — excluded from every scaling fit
Distinct budgets measured 57 344 / 100 000 / 106 496 / 253 952 / 507 904
Cells at the modal budget (106 496) 625 / 825 = 75.8 %
Widest within-game budget span 5.08×

Two controls are enforced in code rather than asserted in prose:

  1. No target leakage. gen_gap is train − unseen and therefore contains the target; it is never a feature. tests/test_symbolic_regression.py::test_design_matrix_never_reads_the_measured_return asserts this directly by perturbing every ret value and requiring the feature matrix to be bit-identical afterwards.
  2. Grouped cross-validation. All metrics come from GroupKFold by seed, so seed 42 never appears in both the fit and the test split of the same configuration. Unshuffled CV over rows would have leaked seed-level repetition into every score.

The support figure bounds the interpretation of everything that follows: this was a configuration sweep, not a budget sweep. Any slope on $\log_{10} N$ reported here is a local gradient fitted over a narrow span, not a scaling exponent tested across decades.

21.2. Search Configuration

Population 1000, 40 generations, tournament size 20, crossover 0.7, subtree/hoist/point mutation 0.1/0.01/0.1, mean-absolute-error fitness, parsimony coefficient 0.005, constant range $[-1, 1]$, random_state = 0. Function set: add, sub, mul, div, log, sqrt, abs, neg, bpow, with every partial function protected (div returns 1 unless $|x_2| &gt; 10^{-10}$; log is $\log(|x| + 10^{-12})$; sqrt is $\sqrt{|x|}$; bpow clips its exponent to $[-3, 3]$) — gplearn rejects unary functions that are not closed over the negatives its own search produces.

Baselines are evaluated on the same two feature sets as the symbolic fit, because a symbolic model given fewer inputs than its competitors cannot lose informatively:

  • protocol = $[\log_{10} N,\ \text{hard},\ \text{memory}]$
  • protocol+game = protocol + one indicator per game (7 levels)

21.3. P1 — Recovering the Forward Law

Cross-validated $R^2$ and MAE over the 825 pooled cells:

Model Features CV $R^2$ CV MAE Program
mean-only null — −0.0003 1.268 —
linear in $\log_{10}N$ protocol 0.3035 1.002 —
quadratic in $\log_{10}N$ protocol 0.3110 0.998 —
random forest (300 trees) protocol 0.4434 0.902 —
symbolic protocol 0.2773 0.998 10 nodes / depth 3
mean-only null — −0.0003 1.268 —
linear in $\log_{10}N$ protocol+game 0.8869 0.343 —
quadratic in $\log_{10}N$ protocol+game 0.8957 0.307 —
random forest protocol+game 0.9121 0.280 —
symbolic protocol+game 0.7381 0.503 9 nodes / depth 4

Paired bootstrap over 2000 resamples of the squared error, $P(\text{symbolic} \succ \text{linear})$: 0.0215 on protocol and 0.0000 on protocol+game. Under identical features, identical folds and identical targets, symbolic regression does not beat the linear-in-log law that scaling analysis assumes by default — it is beaten by it.

The recovered pooled law over protocol is

$$\hat{R} = 2,\text{memory} + 0.103 - \frac{\text{memory}}{\text{hard}}$$

and the result worth stating plainly is what is absent: the search had $\log_{10} N$ available in every tournament and discarded it. The surviving terms are the memory-backbone indicator and its interaction with difficulty — memory backbones carry about $+2$ return in easy, and that advantage is cancelled when hard is set. This is a description of the measured grid, not a scaling law.

Given the game indicators as well, the search returns

$$\hat{R} = \sqrt{;\text{game_maze}\cdot\log(\text{game_bossfight}) + \sqrt{\log(\text{game_bossfight})};}$$

which is a 9-node way of saying something weaker than it looks: with one-hot game inputs the tree mostly partitions on game, and the two games it names are the two whose returns sit furthest from the pooled mean. Neither expression is offered as a law to extrapolate with — 21.6 is where the cost of trying is measured.

Pareto front of the search

The complexity–accuracy front (parsimony sweep) shows the same thing from the other side:

Parsimony Nodes Depth CV $R^2$
0 (none) 862 67 0.2764
0.001 14 6 0.2689
0.005 10 3 0.2773
0.01 10 3 0.2457
0.02 8 3 0.2625
0.05 4 2 −0.1072
0.1 4 2 −0.2213

A 10-node program is as accurate as an 862-node one, and below 8 nodes the fit becomes worse than the mean-only null. There is no accuracy to be bought with complexity here, which is the signature of a search over data whose variance is not a smooth function of the supplied inputs.

21.4. P1 per Game — the Plateau, Measured

Fitting return against $\log_{10}$ budget within each game gives the quantitative version of section 3.13's "curves plateau" statement. The slope is return per decade of environment steps:

Game Cells Budgets measured Slope (return/decade) CV $R^2$ linear CV $R^2$ symbolic $P(\text{sym} \succ \text{lin})$
bossfight 235 100 k, 106 k, 508 k +0.079 0.0316 −0.0993 0.017
dodgeball 160 100 k … 508 k +0.121 −0.0405 −0.0525 0.178
starpilot 170 100 k … 508 k +1.155 0.1043 0.0580 0.133
heist 120 106 k, 508 k −1.137 0.2548 0.2977 0.973
coinrun, maze, jumper 20 / 70 / 50 single budget not estimable — — —

Three readings follow. In bossfight and dodgeball the slope is indistinguishable from flat, and the symbolic search returned a constant in both ($0.008$ and $0.929/0.801$ respectively) — it found no budget dependence whatsoever, and the negative CV $R^2$ says even that constant generalises poorly across seeds. starpilot is the only game with a substantial positive slope ($+1.155$ per decade), recovered symbolically as $\hat{R} = 0.480 \log_{10} N$. heist has a negative slope — the higher-budget cells score lower — and is the single case where the symbolic fit beats the linear one ($P = 0.973$), returning $-0.952 + \log(\log_{10}N) - \text{hard}$; with two budgets and a descending mean this is best read as the 106 k cells being the better of two configurations rather than as a budget effect.

Measured scaling across budgets

21.5. P2 — the Inverse Problem: Is the Architecture Identifiable from its Return?

The hidden parameter is now the configuration family that generated a cell. Within one stratum $(\text{game}, \text{suite}, \text{budget})$ the protocol is identical for every cell, so the only observable left is the measured return. The reference profile of each family is built by averaging $k$ seeds; the query is a return averaged over $j$ held-out seeds. Recovery is scored against the $1/F$ chance level.

Stratum Families $F$ Chance $1/F$ Accuracy at $k{=}1$ Accuracy at $k{=}4$ Wilson 95 % CI ($k{=}4$)
bossfight / hard 6 0.167 0.158 0.200 [0.095, 0.373]
bossfight / main 9 0.111 0.094 0.222 [0.125, 0.363]
dodgeball / main 9 0.111 0.133 0.289 [0.177, 0.434]
starpilot / main 9 0.111 0.128 0.200 [0.109, 0.338]

Pooled across strata, as a function of how many seeds the reference averages ($k$) and how many the measurement averages ($j$):

Reference seeds $k$ $j{=}1$ $j{=}2$ $j{=}3$
1 0.133 0.145 0.152
2 0.155 0.141 0.157
3 0.172 0.173 —
4 0.203 — —

Identifiability of the hidden family

A single measured return barely identifies the architecture that produced it. At $k{=}1, j{=}1$ — the situation of anyone reading one benchmark number — accuracy is 0.133 against a chance level of 0.111–0.167. The best cell of the grid, $k{=}4, j{=}1$ at 0.203, still overlaps chance for the $F{=}6$ stratum. Accuracy improves monotonically in $k$ (cleaner reference profiles) and only weakly in $j$ (a less noisy query), which locates the loss precisely: the between-family differences at this budget are smaller than the between-seed noise within a family.

This is the identifiability counterpart of section 3.8's "no top-1 vs top-2 difference is statistically significant with 5 seeds", and it is the stronger statement: it is not that the ranking cannot be tested at $n{=}5$, but that the generating configuration cannot be recovered from a single run at all. The degenerate "always predict the lowest-mean family" heuristic scores a constant 0.111 / 0.167 — exactly $1/F$ — confirming the chance floor is computed correctly.

Caveat, stated rather than hidden: the $(\text{reference}, \text{measurement})$ splits within a stratum reuse seeds, so the $n \le 3960$ predictions are not independent draws and the Wilson intervals are optimistic. They are reported as a spread indicator, not as a confirmatory test.

21.6. P3 — Inverting the Law for Budget

Solving the fitted within-game line for the budget a target return implies, and comparing against the cell that actually achieved it:

Game Target return Budget implied by the law Budget of the cell that achieved it Ratio Inside measured support?
starpilot 3.70 1 816 622 507 904 3.58× no
bossfight 0.49 41 301 266 431 507 904 81 317× no
dodgeball 1.98 1 203 205 443 903 253 952 4 737 925× no

No inversion lands inside the budget span that was actually measured, so under the support criterion zero of the three estimable games is budget-identifiable. The best returns in bossfight and dodgeball are not high-budget returns, and a monotone line fitted to budget cannot reach them without extrapolating by five to six orders of magnitude. Those numbers are reported to make the extrapolation visible, not as predictions: a law fitted over a 5× span has no authority at $10^{12}$ steps.

This is the same conclusion as section 3.13's budget-scaling experiment — a 5× budget "neither created nor eliminated an advantage" — reached from the opposite direction: not by running more steps, but by asking what the fitted line implies.

21.7. Threats to Validity

  1. Support. 75.8 % of cells sit at one budget and the widest within-game span is 5.08×. Every slope on $\log_{10} N$ here is a local gradient of a curve measured over less than one decade. A genuine scaling study needs decades of budget, which section 3.13 began and this section does not extend.
  2. Game identity is not a discovery. $R^2$ rising from 0.30 to 0.89 when game indicators are added says that maze returns and bossfight returns live on different scales. That is expected, and its only use is as a control: it bounds how much the protocol variables can explain once scale is absorbed.
  3. Family granularity is a modelling choice. FAMILY_OF maps the 40 configurations present in the 825 rows onto 15 families (cbam/spatial → cnn_attention, impala/impoola/resnet18 → cnn_deep, curl/cpc/acl/contrastive → contrastive, …); the dictionary carries 44 entries, of which a few belong to suites not analysed here. An unmapped configuration raises rather than being dropped, so the mapping cannot silently drift as suites grow. Coarser grouping raises the identifiability ceiling in P2 — with $F = 15$ overall but $F = 9$ inside the largest stratum — and the mapping is declared in the script, not inferred.
  4. Search stochasticity. gplearn is seeded (random_state = 0) and the study was executed twice from the same committed inputs: every metric of P1 (pooled and per game), P2 and the Pareto sweep reproduced identically — the only differences between the two artefacts were cosmetic (feature labels and one rendered equation). The numbers above are therefore from a reproducible run, not a lucky draw. Re-running with a different seed is expected to explore another part of the same plateau rather than find a better law, and the Pareto sweep of 21.3 is the evidence for that claim: 862 nodes buy nothing over 10.
  5. Version drift between stacks. The 825 cells were produced by the JAX port; port-vs-SB3 parity is documented in section 15. This section describes the port's grid, and the SB3 study's tables in sections 3 and 7 are deliberately not mixed into it.

21.8. Artifacts and Reproduction

Artifact Content
symbolic_regression.py producer: loads the committed cells, runs P1/P2/P3 and the Pareto sweep, draws the three figures
results/symbolic_regression.json every number in this section, with _provenance (engine versions, search settings, budget support, leakage guard)
results/symbolic_regression_pareto.png nodes vs CV $R^2$, with the linear-in-$\log_{10}N$ reference line
results/symbolic_regression_scaling.png measured return vs budget per game, with the fitted per-decade slope
results/symbolic_regression_identifiability.png P2 recovery accuracy vs reference seeds, with the $1/F$ chance line
tests/test_symbolic_regression.py 11 tests: the no-leakage assertion, family-map completeness, Wilson interval behaviour, and a real gplearn fit that fails CI if the gplearn/scikit-learn interface drifts again
py -3.10 symbolic_regression.py            # full study (search: 1000 x 40)
py -3.10 symbolic_regression.py --quick    # small search, for a fast local check

Dependencies are pinned in requirements.txt: gplearn==0.4.2 with scikit-learn==1.5.2. The cap is not cosmetic — gplearn 0.4.2 calls an estimator helper that scikit-learn removed in 1.6, so with a current sklearn SymbolicRegressor.fit raises AttributeError instead of searching. That failure is caught by the CI test above rather than mid-study.

About

Reproducible RL benchmark on Procgen - 5-seed, 100k-step comparisons of visual architectures, world models, exploration bonuses, offline RL and SMAX multi-agent, plus a JAX/Flax port of the same protocol.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages