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 (
CNNvsAttentionvsWorld ModelsvsAugmentvsNew ArchsvsExploration) with 5 seeds (42-46), 5 games (bossfight,starpilot,dodgeball,maze,heist+coinruncontrol), and two difficulty settings (easy 200/hard 200/eval 0) inProcgen(~300 FPSoncuda,50kin~3 min). All experiments useframe_stack=1(3×64×64CHWuint8),PPO(lr 3e-4,n_steps 256,batch 64,n_epochs 3,γ 0.99,λ 0.95,clip 0.2), andtensorboardfor training monitoring. Evaluation initially used10 episodesand was systematically hardened throughout the study up to the definitive protocol:100 epsstochastic + deterministic on unseen levels (seed+1000) — sections 3.10→3.12.
-
The evaluation protocol dictates conclusions — and
10episodes are insufficient. Each protocol upgrade (10→30→100 eps, unseen levels withseed+1000, dual stochastic+deterministic evaluation) reshuffled rankings: the global leader dropped (spatial 1.54 → 1.35),ICMlost its lead inmaze/heistobserved at30 eps(spurious variance), andvitcollapsed indodgeball. An enduring lesson: inProcgen, rankings evaluated with<100episodes are unreliable (sections 3.10–3.12). -
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 onlymlp_vector(MLP over16×16grayscale,256D) remained at the top across all protocols (1.25@10 → 1.36@30 → 1.43@100) and wins instarpilot. Per-game winners:aug_crop(bossfight),mlp_vector(starpilot),resnet18(dodgeball) (section 3.12). -
World Models and exploration: contextual conclusions, not universal. WMs underperform only in
bossfight(<0.5); instarpilot/dodgeballthey match standard CNNs — the initial "WM is weak" claim was an artifact dominated by a single environment. Curiosity methods (ICM/RND/NGU) tie with standardPPOinmaze/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 threeheistarms 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). -
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: a5×budget neither created nor eliminated an advantage, and the generalization gap remained≈0(no memorization up to500k). With5 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 inbossfight/dodgeball, and the measured return identifies the generating architecture family at 0.133 accuracy against a chance level of 0.111–0.167. -
In HRL, the primary driver is temporal abstraction — and learned skills only win when timing matters. In
jumper, action-repeat (skip4) yields4×the return of flat RL, and hierarchy with fixed skills adds nothing further; inplunder, only hierarchy with learned skills wins (4.16vs3.53,+18%) and yields a deterministically exploitable policy (det 2.70vs≤1.32). Whether "hierarchy helps" is strictly game-dependent (section 11.1).
- Wrapper
procgen_wrapper.py:6ProcgenGymWrapper(gymn.Env)— convertsgym 0.26.2legacy 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 variantprocgen_wrapper.py:55ProcgenVectorWrapper(16×16grayscale→256DMLP) for control comparison:same game with/without computer vision. - Factory
procgen_wrapper.py:85make_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'(Procgenstandard); Eval:num_levels=0(unlimited, never-before-seen levels) withseed+1000— measures generalization. - No Frame Stacking:
frame_stack=1,Box(3,64,64)uint8across all benchmarks (procgen_wrapper.py:21).frame_stack=4(12×64×64) is supported (procgen_wrapper.py:19) but unused;Imitation-playeruses128×128×4.
- Classic CNN
models/sb3_extractors.py:8ClassicCNNExtractor—Conv 32 8×8 s4 → 64 4×4 s2 → 64 3×3 s1 → Flatten → FC 512(600kparams), auto-detectsHWC/CHW(is_hwc). - Attention CNN
models/sb3_extractors.py:63AttentionCNNExtractor(use_cbam)—CBAM(ChannelAttentionreduction 16+SpatialAttentionkernel 7)models/cnn_attention.py:82or purelySpatialAttentionModulemodels/cnn_attention.py:6(x * attention_map + xwith residual connectionmodels/cnn_attention.py:37to stabilize purespatialattention, which collapsed to0.00deterministically).FC 512. - World Models & Auxiliary Visual Representations
models/world_model_extractors.py:6—VAEExtractor(latent 128, KL)+dream()deconv, deterministicAEExtractor+dream(),ReconExtractor(L2dec 3×64×64) +dream(),ContrastiveExtractor(InfoNCEnoise 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 injax_port/dreamer.pyandjax_port/train_dreamer.py. - Contrastive Augmentations
compare_augment_contrastive.py:14ContrastiveCrop(pad 4 + random 64),ContrastiveColor(brightness 0.8-1.2),ContrastiveNoise(noise 0.01). - New Architectures
models/combined_extractors.py—ImpalaCNNExtractor(stack of convolutionalImpalaBlockmodules),ImpoolaCNNExtractor(GAP 64D),LSTMAttentionExtractor(CNN + LSTM 256 + attention),ViTExtractor(64 patches 16×16 + 4-layer Transformer),ResNet18Extractor— all withFC 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(extendsRNDWrapperwith episodic novelty memory,reward += beta * bonus * episodic) — evaluated onmaze/heist(benchmark #7).
- Training:
5 seeds(42,43,44,45,46) with fixedPPOseed+procgen rand_seed—mean±stdacross seeds recorded instatistics.json. - Evaluation:
10 episodesdeterministic=False(stochastic, correctingspatialwhich gave0.00deterministic vs4.00stochastic in8ktests) oneval 0. - Monitoring:
tensorboard --logdir logs_*(events.out.tfevents.*).
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.
| # | 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 |
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.0outperformsclassic 7.6;MLP 8.0matches the bestCNN—coinrun easy 200is purely reactive and does not require complex spatial vision (downsample 256Dsuffices). PureSpatialattention proved unstable (0.0on 1 seed) without a residual connection.
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 |
contrastiveperforms best, but all remain<0.5—bossfight 100kis insufficient when trained standalone.
⚠️ Methodological & Architectural Clarification (Gradient Flow & Auxiliary Pretext Objectives): In the root PyTorch / SB3 benchmark (compare_world_models.py), the modules inmodels/world_model_extractors.pyserved as customBaseFeaturesExtractorarchitectures within standard Stable-Baselines3 PPO. Duringmodel.learn(), SB3 backpropagates gradients exclusively from the PPO policy and value heads throughforward(). 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, anddream()) 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 injax_port/train_dreamer.py.
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:
cropoutperformscolor/noiseinstarpilot(+31%) anddodgeball;colortakes second inbossfight. Overall:classicwinsdodgeball,spatialwinsstarpilot— rankings invert across games; a single game introduces significant inductive bias (theProcgenstandard is16games;3is the bare minimum).
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) |
hardcompresses all returns to0.0-0.4(compared to0.5-0.76ineasy);CNNcollapses in100k—hardwould require200k(~6h) and does not warrant a fullsuite hard;easyconstitutes a fairer benchmark.
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/ResNetfail to surpassspatial 2.6instarpilotorclassic 1.48indodgeball;lstmwinsbossfight/starpilotbut underperforms indodgeball.
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
rndandnguarms ran with broken linear projections (nn.Linear(1024, 512)instead of2304) that raised exceptions silently swallowed byexcept Exception: pass, producing identical clones of vanilla PPO. The re-measurement with verified dynamic stem sizing inmodels/bonuses.pyrecorded 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, whilerndachieves a modest advantage onheist(1.04vs0.72), exploration bonuses without running variance normalization (Burda et al., 2018) fail to significantly break away from PPO. Note that the uniform random baseline scores4.40±5.01onmazeand2.80±4.54onheistunder 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.
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 beta × (MSE / trailing-window mean of that MSE) — divisive reward normalization, chosen because it stays positive, monotone in novelty, and 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,
| Arm |
maze mean |
95% CI |
heist mean |
95% CI | ||||
|---|---|---|---|---|---|---|---|---|
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 threeheistarms survive ($p_{adj}$ 0.0028–0.0174) and onmazeonlyrnddoes ($p_{adj}=0.0081$ ;icm0.071,ngu0.107, andngu's interval overlaps the control's). At the 10-episode protocol the ordering is the same (maze3.00/4.00/2.80 vsppo1.80;heist4.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 returnmaze0.00 (icm) / 0.70 (rnd) / 0.46 (ngu) andheist0.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.
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 3are pureCNNvariants (spatial/resnet/classic);World Models(vae 0.88,recon 0.90) andcontrastive 0.97rank belowMLP 1.25insuite 100keasy—CVwith attention still outperformsWorld ModelsinProcgenat100k.
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 100kworld models) are now read fromresults/legacy_records.json, the single machine-readable record of those runs — they used to be literals pasted insidescorecard_analysis.py.tests/test_legacy_records.pyasserts that those arrays still reproduce themean/std/ci95published below, and the same file supplies the section 3.7 ranking used byretrain_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. Onlydodgeball resnet vs vit(d=0.956) approaches a reliable effect. The exact ties betweenRND/NGUandPPO(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_vectorsurpassesspatial).
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/ngurows in this table are the disabled-wrapper arms of section 3.6, not curiosity results. Finding:starpilot_impaladisplays 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).
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)
ICMleads inmaze/heist— inverting section 3.6; at30 eps, intrinsic curiosity bonuses become visible. (2)vitfails to hold 2nd place indodgeball(1.20→0.91) — confirming section 3.8 (n=5underpowered). (3) Deterministic evaluation collapses exploration policies (maze2.47 stoch→0.60 det), while gen gap is ≈ 0/negative across most models — models do not overfit/memorize the200training 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 10episodes, and (c) new model weights — newly instantiated training runs subject toCUDA/cuDNNnon-determinism (seen in the5divergent seeds ofwm_vae). Disentanglement example: instarpilot,spatialscored2.63≈ legacy2.60(difference ~0 → factors (a)+(b)+(c) negligible here), whereas inbossfightit dropped from0.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_vectordethronesspatial— the shift stems frombossfight(spatial0.76→0.36), whereasspatialholds instarpilot(2.63≈ legacy2.60) anddodgeball. (2) World Models are not universally deficient: weakness was concentrated inbossfight; instarpilot(wm_ae 2.17,recon 1.97) anddodgeball(wm_ae 1.05) they tie with standardCNNs. (3)aug_cropis the superior data augmentation (bossfight 0.68, game winner). (4)dodgeballyields poor architectural separation (spread 1.21→0.88vs0.68→0.12inbossfight) — low resolution benchmark. Combining re-evaluatednew_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 modelsevaluated under the new protocol.⚠️ Update: The figures above reflect30 eps; definitive100 epsevaluation (section 3.12) shifts the top-1 instarpilot/dodgeballand dissolvesICM's advantage.
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_vectorconsolidates 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): Inmaze,nguachieves2.76,ppo2.58,rnd2.54, andicm2.36; inheist,rndachieves1.04,ngu0.80,ppo0.72, andicm0.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 instarpilot/dodgeballinverts again (lstm→mlp,mlp→resnet18) whilebossfightremains stable — the upper tier is solid, while mid-tier rankings remain fluid. (4) Mean absolute delta |Δ| is0.108between @30 and @100: variance converges, and residual position shifts reinforce section 3.8: with5 seeds, no strict ranking order is mathematically frozen; the study reports credible trends with confidence intervals.
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 —
250krepresents a minor inflection within empirical noise, and500kregresses or ties; a5×budget neither created nor eliminated an advantage. Answer to item #6: larger budgets were not required to differentiate these architectures;100kproved sufficient. (2) Per-game relative advantages persist:mlp_vectorleadsstarpilotat250k(3.23vs2.73) andresnet18leadsdodgeballacross both budgets (1.01/1.00vs0.91) — confirming section 3.12. (3) Generalization gap remains ≈0/negative even at500k— 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
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
resnet18andcnn_mlp_vectorinverts. The single bootstrap constant is nowrliable_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:
- 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 (resnet18wins IQM,cnn_mlp_vectorwins trimmed mean and unnormalized mean) — further evidence that the top tier is a single statistical cluster.- Pairwise Probability of Improvement: Agarwal et al.'s probability of improvement
$P(X > Y)$ computes the probability that a randomly selected seed/run of architecture$X$ yields higher normalized return than architecture$Y$ :
$P(\text{mlp_vector} > \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} > \text{cnn_spatial}) = 0.640$ (95% CI:$[0.427, 0.840]$ ): also non-significant.$P(\text{mlp_vector} > \text{aug_crop}) = 0.633$ (95% CI:$[0.420, 0.827]$ ).$P(\text{resnet18} > \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.- Performance Profiles: The cumulative performance profile (see Figure 4.9,
results/rliable_profile.png) demonstrates thatcnn_mlp_vectorandresnet18dominate higher normalized return thresholds ($\tau > 0.6$ ), whereaslstm_attention,cnn_spatial, andcnn_cbamshow higher probability mass at moderate thresholds ($\tau \approx 0.5$ ). Data serialized inresults/rliable_scorecard.json.- Canonical (Agarwal) normalization covers only 2 of the 3 suite games:
dodgeballis excluded because the reference architecturecnn_classicdoes not beat the empirical random policy there (see section 18.1).mazeandheistare excluded because nocnn_classicarm was ever trained on them. A previously publishedclassic_meansvalue of1.0for those two games was a silent code fallback, not a measurement; it has been removed.
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.
| Bossfight | Starpilot | Dodgeball |
|---|---|---|
![]() |
![]() |
![]() |
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# 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.
Note: Paths such as
D:\mario-ds,D:\mujoco-walker,D:\Imitation-player, andD:\mario64ds-rlrefer 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 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 |
Empirical Behavior |
|---|---|---|---|---|
DT (Decision Transformer) |
Sequence Modeling ( |
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.
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 ( |
| 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 (ppo control on stochastic return (heist arms survive Bonferroni over the six comparisons), while the deterministic readout of the same weights stays 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:
ContrastiveNoisesubclassesContrastiveExtractorwithout altering theforwardpass (compare_augment_contrastive.py:42) — under identical seeds, it is an exact duplicate ofwm_contrastive(identical empirical results across all 3 games). Treat as9independent configurations, not11. Note: theppo=rnd=nguequalities in the 🧠/⚡/🌎/🎲 rows above are not measured ties —rnd/nguran 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,275models 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_attentionremains the sole configuration leading all 4 scorecard axes within a single game; now tied in Performance withcnn_spatial(2.63).resnet18concedes the top spot ofdodgeballtocnn_mlp_vector(1.21vs1.07).ICMleadsmaze/heistin 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 the30 epsprotocol; the definitive100 epsprotocol (section 3.12) reinforcesmlp_vectorat the top (1.43), restoresresnet18to the top ofdodgeball, and equatesICMwith vanillaPPOinmaze/heist.
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 |
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.mp4in this repository's history, so both names above are outputs you generate locally. The only committed dream artifacts arejax_port/dreams/bossfight_dreams.gifandjax_port/dreams/dreamer_imagined.gif. Note also that until the 23/09/2026 extractor fix,AEExtractor.dream()decoded through a second encoder branch thatforward()never used, so an "AE dream" panel from the old code visualized an untrained path; a failed decode now renders a panel labelleddream unavailableinstead of an unlabeled black frame.
- Hardware:
NVIDIA GeForce RTX 4070 Laptop, driver556.29,CUDA 12.5,WDDM,8 GB VRAM,58°C,~30%GPU-UtilduringPPOexecution oncuda(nvidia-smisampled at20: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,easyachieves<1.0(0.76±0.90) andhardcollapses standard CNNs to0.02±0.04—suite 100k(16.5M steps) is the minimum viable scale for statistical ranking;ViTevaluated inbenchmark #6:~4.6 min/runinbossfightbut16-25 min/runinstarpilot(~4×slower than standard CNNs, varying per game environment) — earlier estimates of~15×(Imitation-player:171) stemmed from imitation learning loss computation rather thanPPOrollouts; nonetheless,ViT 1.20global fails to surpasslstmorspatial;Imitation BConly tracked training loss (ResNet 2.90best) without interacting with RL reward signals — benchmark #4 rectifies this.
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-playercompare_models.pyNature 3.48vsResNet 2.90.
⚠️ 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).
| 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) achieve4×the return offlat(0.90); sustaining directional jumps over4consecutive frames is what solves the game mechanics, and the fixed skill library added zero benefit beyond action-repeat. (2)hrl_learnedranks betweenflatand temporal-abstraction arms injumper(2.96) — co-training meta and low-level controllers requires a larger sample budget to match hand-engineered macro-actions. (3)plunder:hrl_learnedis the sole winning arm (4.16vs3.53flat, with lowest standard deviation0.33) — fixed 4-frame firing macros disrupt aiming timing, whereas learned latent skills adapt flexibly; fixed macro arms merely matchflat. (4) Deterministic evaluation collapses policies injumper(0.38–0.66, stochastic exploration is vital), but inplunder,hrl_learnedstands out (det 2.70vs≤1.32) — learned hierarchy produces a more deterministically exploitable policy. Generalization gap forplunder hrl_learnedis+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 42, 43, 44) on both jumper and plunder, evaluated on unseen levels (seed+1000):
| Game |
|
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:
- 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.jumperMechanics: 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$ ). Injumper, temporal commitment over$2-4$ frames maintains jumping momentum, but excessive commitment ($k=8$ ) leads to overshooting platforms.- Definitive Isolation of Hierarchy in
plunder: Inplunder,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 inplundercannot 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 inresults/action_repeat_results.json.
⚠️ Separated from the primary study (which compared architectures with fixedPPO): 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-4policy vslr 1e-4value), rather than an identical configuration. Imposing3e-4uniformly 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 testlr_sensitivity.py(section 12.2:dqn/qrdqntrained at3e-4onstarpilot, 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).
| 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.07vs0.47, a2.3×margin) — in100kvisual steps, on-policy optimization outperforms value bootstrapping, validating the low-data hypothesis (section 1.4). (2) QR-DQN significantly closes the gap (+60%over vanillaDQNinstarpilot:1.04vs0.65) and inbossfight(highly sparse, high variance) it is the sole leading algorithm (0.28vsppo 0.18) — distributional RL provides tangible benefits precisely under high aleatoric/epistemic uncertainty. (3)A2Cmatches or exceedsPPOinstarpilot/dodgeball(2.38/0.89vs2.29/0.65), but collapses inbossfight(0.05vs0.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≈0across all12evaluation 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 uniformNatureCNNextractor 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.
DQNremains virtually identical (0.65→0.66);QR-DQNgains+0.21, which falls within seed standard error (SEof the difference≈0.20withn=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 at100kis not an artifact of learning rate selection (at least instarpilot, the evaluated environment).
This repository transitioned through three distinct phases, all preserved within Git commit history for auditing:
- ProcGen/SB3 Phase (this study, commit
4f84ed3). The original systematic benchmark on realProcGenusingStable-Baselines3/PyTorch— sections 1–12 above. This constitutes the scientific core of the project. - JAX/Craftax/Brax/MARL Phase (commits
5e88c16–6d450fa). An exploration rewriting environments inJAX/Flax/OptaxfeaturingCraftax,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;8Msteps vs100k; throughput measured on non-comparable workloads). - JAX-Port-of-ProcGen Phase (Sep/2026). A systematic initiative to reconstruct the exact study in
JAXto 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.
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.
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 8MvsProcGen 100k) within a singleREADME/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 andrequirements.txt), whereas the exploratory JAX suite requiredPython 3.12+jax 0.11+craftax/brax. Maintaining both within the same virtual environment breaks dependency resolution on both sides (thenumpy<2vsnumpy 2.xconflict 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.
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-onlyconfirms 29src/modules, 20+experiments/benchmarks and the JAXresults/*.jsonartifacts are retrievable (e.g.git show 5e88c16:src/ppo.py). It does not hold for every name in the list above: the throwaway helper scriptssetup_procgen_env.sh,probe_procgen.sh,diag_env.sh,smoke_procgen.py,_verify_pg.pyand_probe_pg3.py, plusboxing_final_results.txtandcombinatorial_grid_results.json, were never committed and are gone. They were scratch tooling and one duplicate ofcombinatorial_matrix_results.json, so nothing in sections 1–19 depends on them.
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.
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 FPSlegacy oncuda), 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.
Prior to implementing learner logic, a phased plan with strict viability gates was established:
- PA0 (Compatibility): Prove that
procgen 0.10.7andjax[cuda12]coexist and operate (env simulation + GPU acceleration) within the same interpreter environment. Exit criterion:PROCGEN_JAX_OK— initialize headlesscoinrunand execute a JAX kernel oncuda:0within the same virtual environment. - PA1 (Throughput): Construct the vectorized CPU→GPU pipeline and measure empirical FPS against the ~
300 FPSSB3 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.
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).
| 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 |
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.jsonregeneratesanalysis_full.jsonfrom that record alone, andtests/test_grid_analysis.pyfails if the two drift apart.analyze_grade.pyresolves 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-2overlapflag injax_port/stats.pyis 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.jsonnum_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_readyper step) dominates overhead: with 1 env, the pipeline sustains300 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 FPSpre-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).
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 |
test_stats:STATS_OKacross both environments (Win py3.10 and Linux venv).test_parity:gym3batching matches single-instancegymstep-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_gradientsyntax; DQN entrypoint hook; device-side gather latency;np.trapezoidcompatibility undernumpy 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 viarun_grade.py --suite <s> --games <g> --seeds 42-46 --timesteps 100000 --eval-full(10 suites, 615/615 cells completed, documented inanalysis_full.json). - Path conventions (updated 23/09/2026): every
jax_port/module resolves its own package directory, sorun_grade.pywrites intojax_port/results_grade/andanalyze_grade.py/analyze_temporal_hard.pyread and write insidejax_port/regardless of the current working directory. No source file hardcodes/mnt/c/...orC:\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 justPYTHONPATH=<checkout> <venv>/bin/python -m jax_port.<module>. The orchestration scripts take overrides instead of literals:marl_10m_run.sh/marl_2s3z_run.shhonorREPO,PY,SEEDS,LR,ENT;continue_grade.ps1honorsREPOandPROCGEN_JAX_VENV(default/root/procgen-jax)..gitattributespins*.shto 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 bymake_figures.pydirectly from serialized JSON results. hrl_learnedat 500k Steps: Onplunderacross 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 onbossfight: VAE BCE 0.37 / KL 0.02, AE 0.32 (~30 s each); generatedjax_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 upstreamimageio-ffmpegaudio argument incompatibility in the venv. - Full Dreamer 1M Steps:
dreamer.py+train_dreamer.py—coinrunseed 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.gifvisual 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 insweep.jsonandjax_port/dreams/. - Dreamer reached by the grid runner (27/09/2026): the
dreamersuite was wired intorun_grade.py's cell builder on 25/09 but never added to--suite's argparsechoices, so--suite dreamerexited withinvalid choiceand the branch had executed zero cells —results_grade/dreamer/did not exist. The published Dreamer rows above come fromtrain_dreamer.pydirectly, 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,coinrunseed 42, 20k frames, 8 envs, 3 eval eps): 424 s at 47 SPS,train_ret_mean203.0,eval_unseen3.33, andanalyze_grade.py'sLoaderparses the cell it wrote (_cfg=dreamer,eval_unseen.meanpresent, 0 unreadable, 0 no-eval). The artifact is deliberately parked inresults_grade/dreamer_smoke/rather thanresults_grade/dreamer/:dreameris inRANKING_SUITES, so a 20k-frame plumbing cell left in the graded directory would be folded intocells_summary.jsonand into everything downstream of it.
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,qrdqnleadsbossfight,a2c ≈ ppo, learning rate sensitivity is minimal. Onplunder,hrl_learnedleads (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_noiseglobal,contrastivestarpilot,resnet18bossfight) differ from SB3 study top models (mlp_vector,mlp,aug_crop), but all pairwise comparisons showd ≤ 0.4with 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
mazeperformance to 5.33 (vs 2.80 in single-env SB3). Conclusion: Discrepancies stem from parallel rollout batch dynamics, not framework differences. Documented inpaired_sb3_big_maze.json. - Environment Reproducibility. Pinned via
jax_port/requirements-jax.txtand documented injax_port/SETUP_VENV.md. - Budget Scaling (§3.13):
mlpexhibits modest gains (2.04→2.74 instarpilot), whileresnet18remains 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):
bossfightimpoola0.20 >mlp0.09 (d=0.73, overlapping CIs);starpilotaug_noise2.63 ≈impoola2.61 (d=0.06);dodgeballimpoola1.29 ≈mlp1.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):
gatachieves ~1.3 global return;spr_aug > spr;acl > curl/cpcacross all 3 games;harddifficulty 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 > mlpacross 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.
-
coinrun50k, 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.json615/615 verified; serialized inanalysis_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 oncoinrun: 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 regularizationsqrt(sum + eps). -
MARL/SMAX Integration (
jaxmarl 0.1.0):jax_port/marl/— 7 algorithms (ippo, mappo, vdn, qmix, mapoca, cte, tarmac) on map3m× 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 in3mwithin 1M steps. Diagnosed that JaxMARL literature requires recurrent GRU-128 + 10M steps, implemented inrecurrent.pywith 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.0where 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.
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 --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):
vdnflat (1M steps × seeds 42–44): 0.000 eval win-rate (returns 0.50–0.56)qmixflat (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) and2s3z(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 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:
- Transformer models do not scale with budget: Relative to the 100k baseline,
transformerremains at the bottom tier in both environments. The hypothesis that "Transformers merely lacked budget" is rejected up to 500k steps. - State space models (
s5,s4,mamba) lead in hard heist, consistent with 100k trends, though confidence intervals overlap with top convolutional baselines. bossfightsuppresses all architectures (0.0–0.2), offering no meaningful temporal differentiation.- 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 (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
| Model | Architecture | SPS | Wall (s) | Train (Last 20 eps) | Eval Unseen (20 eps) | 95% CI | Source |
|---|---|---|---|---|---|---|---|
classic |
NatureCNN feedforward (stateless, |
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 ( |
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 ( |
4,982 | 21.4 s | 3.20 | 1.45 | [0.64, 2.26] | results_stack1_bench_100k.json |
Scientific Findings:
-
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. -
Latent State Overfitting in LSTM:
recurrent_lstmattained 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. -
Stability of State Space Models (S5):
recurrent_s5exhibited near-zero generalization gap (2.25 train$\to$ 2.20 unseen eval), though it did not outperform the feedforward baseline. -
Computational Throughput: Step-by-step recurrent state tracking incurred a ~33–34% throughput penalty (~7.5k
$\to$ ~5.0k SPS). - 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 |
Structural Behavior at |
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 |
Requires explicit FIFO frame buffer. | |
tcn |
Causal dilated conv over 4 frames | Causal conv at |
Requires explicit FIFO frame buffer. | |
| Pure Attention | transformer |
Self-attention across 4 frames | 1-token self-attention is trivial identity ( |
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 done. |
Implemented (recurrent_lstm). |
s5 |
Associative scan over 4 frames | Scan reduces to recursive step |
Implemented (recurrent_s5). |
|
gru |
GRUCell over 4 frames | Requires carry 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_lstmandrecurrent_s5represent the gated recurrent and state-space families, respectively.
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:
-
Memory State Transition Alignment: Auditing the rollout buffer in
jax_port/bench_temporal_stack1.pyrevealed 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$ . -
Hidden Carry Regularization (
RegularizedRecurrentLSTMBackbone): To prevent LSTM hidden state drift and memorization overfitting under low-data budgets, we implementedRegularizedRecurrentLSTMBackboneinjax_port/recurrent_step.py, introducingLayerNormon the recurrent hidden carry$(h_t, c_t)$ along with AdamW weight decay ($1\times 10^{-4}$ ). -
Authentic Spatial Occlusion Benchmark (
caveflyer): Unlikestarpilotwhere all adversaries are visible in the viewport,caveflyerfeatures 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 (seed+1000), persisted in results/caveflyer_multiseed_bench.json:
| Model | Architecture | Mean Return ± Std | 95% Student's |
Cohen's |
Per-Seed Breakdown (42–46) | Source |
|---|---|---|---|---|---|---|
recurrent_lstm |
CNN + LSTM pre-transition carry ( |
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 ( |
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, |
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 ( |
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_lstmretains 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/
SubprocVecEnvvs JAX/gym3C++ 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.
- Gate PA0 and Environment: PPA
deadsnakes(Python 3.10 on Ubuntu 24.04); wheelprocgen-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) / dedicatedpy3.10venv (JAX port).
The multi-agent module (jax_port/marl/) provides cooperative and competitive multi-agent learning architectures implemented in JAX/Flax.
In recurrent multi-agent Q-learning, agents update their parameters through Truncated Backpropagation Through Time (TBPTT) over trajectories of length
-
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$ ).
The recurrent Q-learning Bellman target is given by:
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.
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.
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 (
⚠️ Consequence formazeandheist(must be read together with section 3.12): under this protocol a uniform random policy solvesmazein 22 of 50 episodes andheistin 14 of 50, while every trained arm on those two games lands atmaze$2.76$ –$2.80$ andheist$0.72$ — i.e. below the random point estimate. Taking the standard errors of the anchors ($4.40 \pm 0.71$ S.E. formaze,$2.80 \pm 0.64$ S.E. forheist,$n=50$ ),heistis clearly worse than random (95% CI$[1.54, 4.06]$ does not reach$0.72$ ) whereasmazeis borderline (95% CI$[2.97, 5.83]$ just overlaps$2.80$ ). Procgenmaze/heistineasymode 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) themaze/heistrows 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). Theppo/icm/rnd/nguarms are also where the broken-wrapper defect of section 3.6 was found: the exactppo=rnd=nguequality acrossmazeandheistwas not a null result but a swallowedRuntimeError, now fixed and covered bytests/test_exploration_bonuses.py.
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.
The consolidated scorecard across top architectures (results/rliable_profile.png) presents three complementary panels:
-
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 fromrliable_metrics.NUM_BOOTSTRAPS). -
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$ ). - Multi-Metric Forest Plot: Comparative visualization of IQM, 5% Trimmed Mean, Median, and Arithmetic Mean over the top 5 architectures.
-
Canonical normalization scope:
results/rliable_scorecard.jsonnow recordscanonical_gamesandcanonical_exclusionswith an explicit reason per excluded game, instead of silently substituting a placeholder reference mean.
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,AttentionCNNExtractorwith 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, thatfeatures_dimis 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
DummyPytestshim when pytest was absent, which turned every@pytest.mark.parametrizeinto a no-op and reported green without running the cases; the shim is gone and pytest is pinned inrequirements.txt.
- Validates forward pass output dimensions
-
SB3 Integration Tests (
tests/test_sb3_integration.py): build aPPO("CnnPolicy", ...)on each representative extractor and callpredict(), over both a CHW space (whatProcgenGymWrapperyields) 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 inpreprocess_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 thatresults/legacy_records.jsonstill reproduces the publishedmean/std/ci95of section 3.8 and the section 3.7 ranking used byretrain_analysis.py. -
World-Model Extractor Tests (
tests/test_world_model_extractors.py):dream()must return the input's spatial resolution, must share one encoder withforward(), 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 withbeta. -
Grid Analysis Tests (
tests/test_grid_analysis.py):jax_port/cells_summary.jsonmust still regenerate the publishedanalysis_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 thePROVENANCEmap, 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) inresults/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 provesgen_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 thescikit-learn1.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 bytrain.build_parser()so a flag rename cannot leave a stale hand-built namespace behind. -
test_temporal: memory-backbone shapes andStackVecstacking — previously orphaned, never invoked by the runner. -
test_marl:run_all()executes all five cases —MARLSequentialBuffertemporal 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 onlytest_losses, while this section cited the other four as validation evidence.
-
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 onCarRacing-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.jsonholds the per-seed arrays of the pre-rliablestudy and is study data, not prototype code — the name collision is unfortunate but the two must not be confused during cleanups.
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 jax_port/cells_summary.json. Nothing in this section is simulated — every row is a measurement the grid produced (§15.4).
| 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:
- No target leakage.
gen_gapistrain − unseenand therefore contains the target; it is never a feature.tests/test_symbolic_regression.py::test_design_matrix_never_reads_the_measured_returnasserts this directly by perturbing everyretvalue and requiring the feature matrix to be bit-identical afterwards. - Grouped cross-validation. All metrics come from
GroupKFoldby 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
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 random_state = 0. Function set: add, sub, mul, div, log, sqrt, abs, neg, bpow, with every partial function protected (div returns 1 unless log is sqrt is bpow clips its exponent to
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)
Cross-validated
| Model | Features | CV |
CV MAE | Program |
|---|---|---|---|---|
| mean-only null | — | −0.0003 | 1.268 | — |
| linear in |
protocol | 0.3035 | 1.002 | — |
| quadratic in |
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 |
protocol+game | 0.8869 | 0.343 | — |
| quadratic in |
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, 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
and the result worth stating plainly is what is absent: the search had 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
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.
The complexity–accuracy front (parsimony sweep) shows the same thing from the other side:
| Parsimony | Nodes | Depth | CV |
|---|---|---|---|
| 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.
Fitting return against
| Game | Cells | Budgets measured | Slope (return/decade) | CV |
CV |
|
|---|---|---|---|---|---|---|
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 (starpilot is the only game with a substantial positive slope (heist has a negative slope — the higher-budget cells score lower — and is the single case where the symbolic fit beats the linear one (
The hidden parameter is now the configuration family that generated a cell. Within one stratum
| Stratum | Families |
Chance |
Accuracy at |
Accuracy at |
Wilson 95 % CI ( |
|---|---|---|---|---|---|
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 (
| Reference seeds |
|||
|---|---|---|---|
| 1 | 0.133 | 0.145 | 0.152 |
| 2 | 0.155 | 0.141 | 0.157 |
| 3 | 0.172 | 0.173 | — |
| 4 | 0.203 | — | — |
A single measured return barely identifies the architecture that produced it. At
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
Caveat, stated rather than hidden: the
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
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.
-
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. -
Game identity is not a discovery.
$R^2$ rising from 0.30 to 0.89 when game indicators are added says thatmazereturns andbossfightreturns 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. -
Family granularity is a modelling choice.
FAMILY_OFmaps 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. -
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. - 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.
| 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 |
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 |
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.















