# Training design decisions and compatibility notes

These are current contracts and historical caveats for `src/drl_pinball/train`.

- **Shared train/eval environment is intentional.** Each outer iteration evaluates on the same wrapped CFD environment used for learning. Reset/rollout changes environment state and updates `VecNormalize` observation statistics while it remains in training mode. Evaluation does not update policy weights.
- **Outer “episode” means PPO chunk.** One logged episode is normally `learn(2048)` plus a 360-step evaluation. The Gym environments do not terminate; this is not a Gym episode.
- **Symmetry is default-off.** The wrapper remains available for explicit experiments. Historical runs configured with 0.5 were effectively unaugmented because each learn/eval reset restarted the rollout counter before mirrored training transitions could occur; do not label those models symmetry-augmented.
- **Force aggregation differs by scene.** Karman averages the three controlled-cylinder drag/lift values and targets zero. Illusion sums all three and matches one target cylinder.
- **Calibration stages are sorted by quality.** Stage0 is zero rotation and Stage1 is reference open loop. Their DTW scores are sorted into worst/better anchors; stage number does not imply quality.
- **Reward constants differ.** Karman uses `K_CD/K_CL=50/100`; Illusion uses `12/25` because summed three-cylinder forces are compared with one target-cylinder force.
- **`target_diam` is a historical misnomer.** The value is passed to CelerisLab as `radius = target_diam * L0`. With `L0=20`, ratios 0.75/1/1.5/2 mean radii 15/20/30/40 and physical diameters 30/40/60/80 lattice units.
- **Normalization has three layers.** CelerisLab first area/time-averages sensor observations, the env divides force/sensor channels by calibrated `FORCE_SCALE`/`SENS_SCALE`, and `VecNormalize` learns running observation statistics online. Schema-less legacy artifacts inserted `SENSOR_CC=78` before pre-scaling; active native-v2 artifacts do not. The six-channel `legacy-policy-v1` path remains only to load an existing legacy model with its matching normalizer.
- **`VecNormalize` is online, not calibration.** Phase 0 produces fixed physical/reward scales. SB3 running moments evolve through both learning and shared-env evaluation.
- **Best policy requires best normalizer.** `best_model.zip` is incomplete without `best_vecnormalize.pkl` saved at that same best-selection point. Freeze it for inference.
- **Resume is continuation, not exact replay.** It restores a policy checkpoint and matching normalizer, then constructs a new process/environment. It does not promise exact optimizer, RNG, CFD, wrapper, or trajectory state recovery.
- **Seeds distinguish runs, not deterministic replay.** GPU CFD, CUDA/PyTorch behavior, PPO sampling, and process state prevent that stronger guarantee.
- **Targets must be calibrated artifacts.** Keep `target.npy` (and Illusion harmonics) matched to case, config, geometry, SI, force convention, and `calibration.json`.
- **Result retention is historical.** Checked-in summaries/figures support prior writing. They are not model-release artifacts and do not establish full validation or retraining after code changes.
- **Release boundary.** The release provides code, configs, canonical targets, and compact retained best-policy bundles. Each policy is paired with its same-selection normalizer and provenance; intermediate/final checkpoints, logs, TensorBoard streams, and archived failed runs are excluded.
