Skip to content

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.main runs rendered training; python -m src.train runs 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.pt is 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:

  1. Generation gameplay, with the rest of the population simulated without rendering and one representative zone shown.
  2. Fitness calculation and checkpoint/metrics writes.
  3. The elite's visible validation scenarios, one after another.
  4. Selection, crossover, and mutation.
  5. 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.main for rendered training. Use python -m src.train when a window is not wanted.
  • Training commands now create timestamped run folders under runs/ by default. Set --output-dir to choose another root.
  • Use python -m src.tester for the newest eligible run's elite, or --checkpoint for a particular run checkpoint. --runs-dir changes the search root. A legacy root-level elite_model.pt is still used only when the search finds no run checkpoint.
  • The saved .pt files 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_OUT configuration still describes the base generation duration, while training CLI timeouts must be finite and positive. The unbounded -1 timeout is for interactive Game use, 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 0xC0000005 interruption 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.