Simulation / Systems / Research Notes
Genetic Algorithms in TypeScript: An Interactive Launch Optimizer
A browser simulation where a population of launch velocities evolves under Matter.js physics, wind, gravity, mutation, and draggable targets.
Abstract
Genetic Algorithms in TypeScript is an interactive simulation that evolves launch velocities for a ball trying to hit a target in a bounded physics scene. Each genome is a two-dimensional initial velocity. The simulator evaluates a generation by replaying every genome through Matter.js, scoring hits, minimum distance, and path behavior, then breeding the next generation through elitism, rank-biased parent selection, blend crossover, mutation, and random resets. The UI exposes the important algorithm and environment parameters so convergence changes are visible when gravity, wind, population size, mutation rate, elite share, target size, or the target position changes.
I
Motivation
The system makes a genetic algorithm observable. Instead of optimizing an abstract score in a console, every candidate is a launch trajectory. The interface exposes the best path, ghost attempts, draggable target position, and configurable physics while the population adapts.
The optimizer doubles as a debugging surface. Selection pressure, mutation rate, population size, and environmental difficulty are usually hidden inside algorithm logs. Here, each setting changes the shape of the paths on screen.
The project is intentionally not trying to prove that a genetic algorithm is the best way to solve projectile motion. A closed-form or numerical solver could find many of these shots more directly. Projectile motion gives the algorithm a visible success condition: exploration, exploitation, lucky hits, premature convergence, and recovery from changed conditions appear as geometry rather than as abstract fitness numbers.
- Genome: a two-dimensional launch velocity.
- Environment: Matter.js physics with walls, gravity, wind, restitution, and drag.
- Goal: evolve enough successful launch attempts to satisfy a solved streak.
II
Genome Evaluation and Fitness
Each genome is evaluated by creating a fresh Matter.js engine, placing the ball at the launch point, applying the genome as initial velocity, and stepping the world for a fixed number of frames. During the replay, the simulator records the path, the minimum distance to the target, whether the ball hit the target, and when the hit occurred.
The fitness function strongly rewards hits, especially early hits. Misses are still ranked by closeness, with a small travel-distance tie breaker so that useful motion beats a static failure.
That discontinuity is deliberate. Near misses should guide the population before it discovers the target, but once any genome actually overlaps the target, the search should rapidly prefer true hits over merely elegant arcs. The early-hit bonus then breaks ties among successful shots by favoring simpler, faster trajectories.
III
Physics Model and Termination
The simulator treats every candidate as a deterministic rollout in the same bounded world. A Matter.js engine is constructed per genome, gravity and wind are combined into one field vector, four walls are inserted with restitution, and the ball is launched from a fixed point. The engine advances at 60 Hz until the candidate hits the target, exhausts the frame budget, settles on the floor, or bounces enough times that the attempt no longer carries useful search information.
genome (vx, vy) | v fresh Matter.js engine | v apply net field + walls + ball velocity | v step frames -> record path, distance, contacts | +-- target overlap -> hit score +-- rest / bounce / frame cap -> miss score
The default frame budget is 200 steps. Rest detection stops attempts that reach the floor with velocity below the configured tolerance for consecutive frames. Wall-contact counting stops degenerate attempts that repeatedly bounce around the boundary instead of expressing a useful launch strategy.
IV
State and Configuration Schema
The simulation state can be reconstructed from four groups of data: immutable scene bounds, mutable configuration, target state, and population state. The engine does not store hidden physics state between generations; every genome evaluation creates a new Matter.js world.
type VelocityGenome = { vx: number; vy: number };
type Target = { x: number; y: number; radius: number };
type GeneticSimulationState = {
seed: number;
generation: number;
target: Target;
population: VelocityGenome[];
solvedStreak: number;
bestEverFitness: number;
lastSummary: GenerationSummary;
};populationSize = 42
targetRadius = 22
gravityStrength = 0.98
ballRadius = 12
windDirection = -18 degrees
windMagnitude = 0.18
mutationRate = 0.18
elitePercent = 18
attemptFrames = 200
successStreakTarget = 3
ghostCount = 5V
Selection, Crossover, and Mutation
After evaluation, attempts are sorted by descending fitness. A configurable elite share is copied directly into the next generation. Parents are chosen from the top portion of the ranked population using a rank-biased picker, so stronger attempts are more likely to breed without making the population entirely deterministic.
Children are generated by blending parent velocities, then applying random jitter with probability equal to the mutation rate. A small random-reset group is added each generation to preserve exploration when the current population converges too narrowly.
The algorithm therefore balances three kinds of memory. Elites preserve what is already known to work, crossover searches between plausible velocities, and resets admit that the whole current family may be wrong after the target moves or the wind changes. The population behaves like a set of guesses that remembers winners but keeps a few wildcards alive.
- Elite share defaults to 18% and is clamped to preserve at least two elites.
- The parent pool includes at least the elite set plus extra high-ranking attempts.
- Random resets occupy about 8% of the population.
- Velocity bounds keep candidate launches inside a useful range.
VI
Evolution Algorithm
The implemented algorithm is a compact elitist genetic search over a continuous two-dimensional genome. It is not a symbolic planner and it does not analytically solve the projectile equation. The only objective signal comes from simulated rollouts.
evaluate every genome
sort attempts by descending fitness
copy elite attempts directly
while population is not full:
pick two parents from the ranked parent pool
blend their velocity vectors
optionally jitter vx and vy
clamp velocity bounds
append a small random-reset tail
evaluate the next generationRandom resets occupy roughly eight percent of the next population. In a draggable scene, the target or field can move abruptly; the reset tail prevents a population converged around the old shot from becoming the only genetic material available for the new problem.
VII
Target Constraints and Reconfiguration
The target is constrained so the search problem remains valid and the controls remain usable. Its x coordinate must stay to the right of the launcher by a fixed minimum, its radius must fit inside the stage, and it must avoid the wind panel region so the draggable target does not overlap UI instrumentation.
control change or drag | v copy ranked genomes from previous summary | v resize population to new populationSize | v clamp target to legal bounds | v evaluate immediately under new config | v reset solved streak
Population resizing is rank-preserving. If the population shrinks, the highest-ranked genomes survive. If it grows, new random genomes are appended. That gives configuration changes continuity without pretending old convergence statistics still apply.
VIII
Interactive Controls
The UI is built around parameters that visibly affect convergence. Population size changes the amount of search per generation. Mutation rate controls exploration. Elite share controls exploitation. Gravity, wind direction, wind magnitude, ball size, and target size change the underlying physics problem.
Changing a control reconfigures the simulation immediately. The existing ranked genomes are resized where possible, the target is clamped back into valid bounds, and the population starts adapting under the new conditions.
- Drag the target to make the population solve a new point.
- Change gravity and wind to alter the field forces.
- Pause, reroll, and reset controls make convergence behavior comparable across runs.
- Ghost paths show top attempts beyond the single best trajectory.
IX
Convergence Criteria and Observability
The simulator separates a lucky hit from a solved field. A generation is considered generation-solved only when the hit rate reaches the configured threshold. The overall scene is solved only after that condition holds for the configured number of consecutive generations.
The rendered surface exposes the state variables that matter: generation number, hit rate, best minimum distance, solved streak, current target, best trajectory, ghost trajectories, wind vector, and launch velocity. The visualization is not decorative; it is the diagnostic layer for the search process.
X
Implementation Shape
The implementation keeps the genetic algorithm in a pure simulation module and the visual playback in the React page. The evolution loop can be tested without running animation timing, drag handlers, or SVG playback.
A deterministic Mulberry32 random source is used per seed and generation. A generation can be reproduced exactly, while rerolling the seed or changing the environment still creates a genuinely new run.
- simulation.ts owns genomes, population state, target bounds, evaluation, breeding, and reconfiguration.
- GeneticTsPage.tsx owns controls, SVG rendering, path playback, dragging, and replay timing.
- Matter.js provides collision, gravity, walls, restitution, and ball motion.
- The browser route embeds the simulation as a first-class interactive system.
XI
Rendering and Replay Mechanics
Rendering is a projection of evaluated simulation data, not a second simulation. The best path and ghost paths are already produced during fitness evaluation. The UI converts those point arrays into SVG polylines and animates the ball by stepping through the best path at a fixed replay rate.
bestAttempt.path -> highlighted polyline
topAttempts[1..ghostCount] -> translucent ghost polylines
bestAttempt.genome -> launch velocity arrow
target -> draggable circle and hit halo
wind config -> wind vector panel
summary -> generation/hit/min-distance statsBecause rendering is derived from stored attempts, pausing the animation does not pause the optimizer state itself. It only stops the visible replay on the selected best trajectory.
XII
Tradeoffs and Limits
The implementation favors inspectability over raw optimization performance. Rebuilding a Matter.js world for every genome is more expensive than deriving a projectile equation or pooling physics objects, but it makes evaluation isolated: a candidate cannot inherit velocity, collision state, or sleeping flags from a previous candidate. That isolation is valuable when the simulation itself is the fitness oracle.
The genome is also intentionally tiny. It contains only the initial velocity, so the system cannot learn mid-flight steering, spin control, or multi-action strategies. That limitation keeps the search space understandable: every visible behavior is the result of two numbers interacting with gravity, wind, walls, restitution, and target placement.
- A physics-engine rollout is slower than an analytic projectile solver, but it makes collisions and field changes natural.
- A two-scalar genome is easy to visualize, but it cannot express active control after launch.
- A strong hit bonus accelerates convergence after contact, but it makes the fitness landscape sharply discontinuous.
- Deterministic seeds make behavior reproducible, while reroll and drag controls keep the demo exploratory.
XIII
Results and Design Properties
The completed system demonstrates a stable browser-native evolutionary optimizer whose fitness signal is generated by a real physics engine rather than a hand-written parabola. The model supports target dragging, immediate parameter changes, deterministic seeded generations, replayable best paths, and visible population diversity without requiring a server process.
The main technical result is not that a genetic algorithm can find a projectile velocity; the analytic problem is simpler than that. The result is an inspectable optimization surface where selection pressure, mutation, random resets, field forces, boundary collisions, and solved predicates are all observable in one interface.
- The genome is minimal: two velocity scalars are sufficient to produce rich trajectory behavior.
- The fitness function is discontinuous at target contact, which creates a clear separation between near misses and hits.
- The 75% hit-rate solved predicate avoids declaring success from a single lucky candidate.
- The visual replay layer makes convergence auditable through paths, ghost attempts, hit count, and target distance.