AI-Pong 1.4.0
This release documents the project work accumulated after 1.3.0: project and documentation infrastructure, reproducible training runs, validation and checkpoint artifacts, genetic-algorithm corrections and performance work, rendered presentation and audio, and the continuous rendered-training lifecycle. This page is the detailed reference for command behavior and saved run data.
At a glance
python -m src.mainruns rendered training;python -m src.trainruns headless training. Both create a new population and a timestamped run folder.- The default population is 200, the seed is 334, generation timeout is 12 seconds, and each generation validates the fitness winner on 2 scenarios by default.
- Fitness metrics and the current generation's top two models are saved before
validation.
elite_model.ptis overwritten each generation and is not a best-ever or necessarily validation-passing model. - Rendered training uses one Arcade window for gameplay, fitness, visible validation, and reproduction. Headless validation uses the same scenarios and fixed simulation step without a window.
- Run artifacts do not contain a complete population or lifecycle snapshot; they cannot resume evolution.
Training commands
Run these commands from the repository root. Install the runtime dependencies
with python -m pip install -r requirements.txt, or install the package and
development tools with python -m pip install -e ".[dev]".
| Command | Behavior |
|---|---|
python -m src.main |
Rendered population training with generated sounds enabled. |
python -m src.main --no-sound |
Rendered training with sound disabled. |
python -m src.train |
Headless population training with periodic progress logs; silent by design. |
python -m src.main --help |
Show rendered training options, including --no-sound. |
python -m src.train --help |
Show headless training options. |
python -m src.tester |
Open a one-player viewing game using the newest eligible saved elite. |
Both training commands accept the following options. The shared parser is
implemented in src/training_cli.py; the entry points are src/main.py and
src/train.py.
| Option | Default | Meaning and constraints |
|---|---|---|
--population N |
200 |
Individuals per generation; at least 2. |
--generations N |
Omitted | Stop after N generations; at least 1. If omitted, continue until interrupted. |
--seed N |
334 |
Seed Python, NumPy, PyTorch, and CUDA generators when available. |
--timeout SECONDS |
12 |
Finite, positive generation duration. The training CLI does not accept -1. |
--output-dir PATH |
runs |
Parent directory for this invocation's timestamped run directory. |
--fps RATE |
144 |
Base update rate; must be finite and positive, with a finite product when combined with speed and steps per frame. |
--speed MULTIPLIER |
2.5 |
Update-rate and paddle-speed multiplier; must be finite and positive, with a finite product when combined with FPS and steps per frame. |
--steps-per-frame N |
15 |
Simulation steps per rendered update; at least 1, with a finite overall update-rate product. |
--torch-threads N |
1 |
PyTorch CPU intra-op threads; at least 1. |
--validation-games N |
2 |
Fresh validation scenarios per generation; at least 1. Use 20 for a larger suite. |
--no-sound |
Rendered: sound on; headless: silent | Present on src.main only; disables generated sounds. |
For example:
python -m src.train --population 100 --generations 50 --seed 12
python -m src.train --timeout 30 --output-dir D:\training-runs
python -m src.main --fps 60 --speed 1.5 --steps-per-frame 4 --generations 2
python -m src.main --generations 2 --no-sound
FPS × SPEED is the Arcade update rate; multiplying that by
STEPS_PER_FRAME gives the approximate simulation-step workload per second.
The corresponding step duration is 1 / (FPS × SPEED × STEPS_PER_FRAME).
In rendered mode, Arcade owns the update and draw loop. In headless mode, the
simulation advances synchronously and logs progress about every tenth of a
generation, including measured steps per second. Headless completion exits;
rendered completion leaves the window open with a completion status until it
is closed.
Viewing saved models
python -m src.tester searches runs/ for */elite_model.pt and chooses the
candidate with the lexically newest timestamped parent-directory name (using
file modification time as a tie-breaker). If no run checkpoint exists, it
falls back to the legacy elite_model.pt in the current working directory.
The viewer accepts:
| Option | Default | Meaning |
|---|---|---|
--checkpoint PATH |
No explicit path | Load this model file instead of searching for the newest run. |
--runs-dir PATH |
runs |
Search this run-artifact root. |
--no-sound |
Sound on | Mute the rendered viewing game. |
Examples:
python -m src.tester --runs-dir D:\training-runs
python -m src.tester --checkpoint D:\training-runs\20261003T120000.000000\checkpoints\p0gen4.pt
python -m src.tester --checkpoint D:\models\elite_model.pt --no-sound
Paths are interpreted relative to the process working directory unless
absolute paths are supplied. Training writes only beneath the configured
--output-dir; it does not automatically copy the new elite to the legacy
root-level filename.
Run directory and artifact formats
Each invocation creates a new subdirectory named with the local timestamp and
microseconds, for example runs/20261003T120000.123456/. The settings file is
written when the run starts. Artifact paths and the stored
output_directory are rooted at the process working directory unless an
absolute --output-dir is used.
| Path | Format and contents | When written |
|---|---|---|
settings.json |
Configuration and environment metadata described below. | Once, before the first generation. |
metrics.csv |
One generation row with fitness and game-score statistics. | After fitness has been calculated and the population ranked. |
checkpoints/p0gen<N>.pt, checkpoints/p1gen<N>.pt |
PyTorch state dictionaries for ranks 0 and 1 in generation N. | After ranking and before that generation's validation. |
elite_model.pt |
PyTorch state dictionary for the rank-0 fitness winner. | At the same time as generation checkpoints; overwritten each generation before validation. |
validation.csv |
One row of completed-suite summary statistics per generation. | Only after the full validation suite completes. |
validation_scenarios.jsonl |
One JSON object per completed suite, containing the generation and scenario result objects. | Appended after the full validation suite completes. |
settings.json records: created_at, seed, population_size,
generations (null when unlimited), render, timeout_seconds, fps,
speed, steps_per_frame, output_directory, elite_factor,
crossover_rate, mutation_scale, mutation_probability, Python/NumPy/
PyTorch versions, torch_device, torch_threads, sound_enabled, the global
variables mapping, and a nested validation object. That validation object
contains its seed, game count, duration and paddle-width ranges, and win and
shutout target rates.
The CSV columns are:
metrics.csv:generation,best_fitness,mean_fitness,median_fitness,player_score,cpu_score,player_hits,cpu_hits.validation.csv:generation,games,wins,losses,ties,win_rate,shutouts,shutout_rate,win_rate_target,shutout_rate_target,reached.
Each object in the JSONL scenarios array includes the randomized inputs
(seed, duration, paddle_width, ball_x, ball_y,
ball_vertical_direction, ai_paddle_x, and cpu_paddle_x) and outcomes
(player_score, cpu_score, win, and shutout). reached in
validation.csv is a run-level latched indicator: once any generation reaches
both targets, later rows also show true. The rate columns remain specific to
the row's generation.
Fitness ranking, metrics, and model checkpoints are saved before validation starts. Validation summaries and scenario details are appended only after all scenarios finish. If a run stops partway through a validation suite, its fitness artifacts may exist without a validation row for that generation.
What the elite checkpoint means
After fitness calculation, the population is sorted by fitness and
elite_model.pt is written from the first-ranked individual. The file is
overwritten every generation; it represents the most recently checkpointed
generation's fitness winner, not the best model ever seen. It is saved
before validation, so it is not guaranteed to meet the validation target.
src.tester selects the newest run that has such a file, not the run with the
highest fitness or validation rate.
Training always constructs a new initial population when invoked. The seed controls the Python, NumPy, PyTorch, and available CUDA generators, but does not guarantee bit-for-bit repeatability across software versions, devices, or hardware. It does not restore a prior population. Artifacts do not save all individuals, optimizer/evolution state, or complete RNG and lifecycle state; there is no full training resume command.
Validation and selection behavior
Recommendation: use --validation-games 20 or higher for a more informative
assessment of an elite's reliability. The default of 2 prioritizes quick
developmental feedback, not a robust evaluation. Larger suites increase runtime,
particularly in rendered mode, and do not guarantee performance in all scenarios.
By default, the generation's fitness-ranked elite is evaluated on 2 fresh
scenarios. The suite uses durations from 6 to 18 seconds, paddle widths from
50 to 120 pixels, randomized ball and paddle positions, and either vertical
ball direction. Scenario definitions are derived from the run seed and
generation: the scenario-list generator is seeded with
run_seed + generation * 1_000_003, and scenario i uses seed
run_seed + generation * validation_games + i + 1 (with i starting at 0).
The ball's horizontal start range is 10 pixels from either court edge; its
vertical range is 100 pixels either side of court center. Paddle centers are
constrained so the entire paddle lies within the court.
Validation uses a fixed step duration derived from the configured update rate and steps per frame. Headless evaluation runs to completion synchronously; rendered evaluation uses the same scenario setup and result aggregation but advances a bounded number of steps in each Arcade update. Python, NumPy, PyTorch, and available CUDA RNG states are saved and restored around validation, so evaluation does not consume the training randomness used by selection and reproduction.
The success target remains at least 95% wins and 90% CPU shutouts. With the
default 2 games, both must be wins and CPU shutouts; such a small sample is
only a quick feedback signal, not strong evidence of reliability. With
--validation-games 20, the target requires at least 19 wins and 18 shutouts.
A win requires the player score to exceed
the CPU score; a shutout requires the CPU score to be zero. Reaching both
targets sets a persistent green indicator and is logged, but does not stop
training. --validation-games changes the scenario count; the randomized
ranges and rate thresholds are not CLI options.
Selection keeps the top 10% as elites, copies elites into the survivor pool,
and probabilistically retains other individuals according to relative
fitness. The population is restored to the configured size. Crossover uses
the top 40% mating pool (at least two parents, adjusted to an even count),
randomly pairs parents, and creates two directional offspring per pair.
Offspring are reset and mutated with scale 0.2 and the network's default
per-parameter mutation probability 0.1.
The crossover helper flattens layer-neuron positions across the network, copies selected neuron weights and biases, and carries selected neurons' outgoing weights from the other parent. It checks that parent architectures match. The implementation samples two distinct cut points and uses the half-open interval between them, ensuring at least one neuron is selected. Adjacent cut points select exactly one neuron. An interval can cover only part of the network; crossover does not require selecting neurons in every layer.
Rendered lifecycle and visuals
Rendered training owns one Arcade window and one event loop across:
- Generation gameplay, with the rest of the population simulated without rendering and one representative zone shown.
- Fitness calculation and checkpoint/metrics writes.
- The elite's visible validation scenarios, one after another.
- Selection, crossover, and mutation.
- The next generation in the same window.
Fitness calculation is spread across bounded batches of 16 individuals per update. Validation uses bounded fixed-step batches on Arcade's main thread, so the window can continue drawing and processing close events. The status area shows generation, phase, validation scenario/count, live score, and cumulative validation W/L/T. The visible gameplay representative is a UI choice; after fitness ranking, the first-ranked elite is the one checkpointed and validated. Closing the window cancels validation if active and prevents later lifecycle phases or generations from starting. A finite generation limit leaves the window open at completion until it is closed.
The refreshed rendering uses an arena background of RGB (13, 24, 39), a
muted court-marking color (42, 62, 82), dashed center markings and center
circle, teal player paddles (57, 218, 193), orange CPU paddles
(255, 171, 92), and a blue ball halo (100, 181, 246). The side panel
renders the network architecture, positive-weight blue (79, 164, 255) and
negative-weight coral (255, 104, 112) connections, activations, and active
output. These are presentation changes and do not feed observations or alter
game physics.
Generated sound
Rendered src.main and src.tester synthesize short tones through Pyglet;
there are no bundled audio files or added audio dependency. Only the displayed
zone routes paddle-hit, wall-bounce, and scoring events. Per-event cooldowns
limit repeated tones; finished players are pruned when another sound is played,
and all remaining players are released when the window closes:
| Event | Frequency | Duration | Volume | Cooldown |
|---|---|---|---|---|
| Wall bounce | 520 Hz | 0.045 s | 0.07 | 0.055 s |
| Paddle hit | 760 Hz | 0.065 s | 0.10 | 0.055 s |
| Score | 360 Hz | 0.14 s | 0.16 | 0.18 s |
Sound does not affect physics or fitness. Use --no-sound on either rendered
command to mute it; headless training is silent. The user has confirmed that
the sound effects work.
Project and documentation maintenance
The project now declares package metadata, Python 3.10+ support, runtime
dependencies, and optional development (pytest, pytest-cov, ruff) and
documentation (mkdocs-material>=9.5,<10) dependencies in pyproject.toml.
The repository includes Ruff configuration and pytest coverage; GitHub Actions
CI runs Ruff and pytest with coverage on Ubuntu and Windows under Python 3.10
and 3.12. Dependabot, issue and pull-request templates, contributor
instructions, and a MkDocs Material site deployed by the GitHub Pages workflow
complete the project-maintenance work.
Key implementation surfaces are src/training_cli.py (shared training
arguments, validation, and seeding), src/ga/ga_core.py (evolution, artifact
writing, and rendered lifecycle), src/ga/validation.py (scenario generation,
evaluation, and RNG isolation), src/utils/functions.py (crossover),
src/game.py (simulation/window lifecycle and presentation), src/audio.py
(synthesized effects), and src/components/colors.py /
src/components/playzone.py (visual palette and zone rendering). Focused
automated coverage is in tests/test_training.py, tests/test_ga.py,
tests/test_game.py, and tests/test_audio.py.
The documentation and tests cover architecture, fitness, coordinates, training, generated audio, scenario validation, run artifacts, crossover, and rendered lifecycle behavior. These automated lifecycle tests do not replace a manual multi-generation window check.
Migration notes
- Keep using
python -m src.mainfor rendered training. Usepython -m src.trainwhen a window is not wanted. - Training commands now create timestamped run folders under
runs/by default. Set--output-dirto choose another root. - Use
python -m src.testerfor the newest eligible run's elite, or--checkpointfor a particular run checkpoint.--runs-dirchanges the search root. A legacy root-levelelite_model.ptis still used only when the search finds no run checkpoint. - The saved
.ptfiles contain network weights, not a population snapshot. Starting either training command begins a new evolution run; there is no automatic import of an old elite into the new population. - Existing
TIME_OUTconfiguration still describes the base generation duration, while training CLI timeouts must be finite and positive. The unbounded-1timeout is for interactiveGameuse, such as the model viewer, not for the training CLIs.
Known limitations and pending checks
- The user confirmed that generations and validation remain visible, but stopped the 20-scenario run at 3/20 because validation took too long. Full suite completion and the finite-run completion display still need confirmation. The default was reduced to 2 scenarios for faster feedback; larger suites remain explicitly configurable.
- A native
0xC0000005interruption was reported. Its cause has not been diagnosed and this release does not claim to fix it. - Checkpoints do not preserve the full population, generation/lifecycle state, or RNG snapshots, and the per-run elite file is neither best-ever nor necessarily validation-passing.
- The browser demo and release/distribution process remain planned roadmap phases; this documentation does not claim either has been completed.