Flappy Bird (NeatapticTS)

This folder is the repository's most complete answer to a practical question: what should neuroevolution feel like when it leaves toy problems and enters a real runtime?

The example uses a Flappy Bird-style control task, but the game is not the real subject. The real subject is systems design under evolutionary pressure: deterministic world stepping, fairer policy comparison, staged selection, worker-backed playback, and a browser UI that teaches instead of merely entertaining.

If you want one example that shows the repo's broader taste in architecture, reproducibility, and educational tooling, start here.

What This Folder Is Trying To Teach

This example is organized around four reader questions:

  1. How do you connect a NEAT population to a repeatable control problem instead of a one-off demo loop?
  2. How do you reduce lucky-rollout bias so evolution rewards robust behavior rather than fortunate seeds?
  3. How do you replay and inspect evolved behavior in the browser without moving simulation authority onto the main thread?
  4. How do you make the resulting network understandable enough to teach from, not just flashy enough to watch?

The folder matters because it answers all four questions in one coherent system. Training, evaluation, simulation, playback, and inspection are separate boundaries on purpose. That separation is what keeps the example readable when it grows beyond a single-file demo.

Choose Your Route

Different readers want different first files. Use the route that matches your question instead of reading everything linearly.

If you want to... Start here Then read
Run training immediately trainFlappyBird.ts trainer/README.md, evaluation/README.md
Reuse the example from code index.ts flappyEnvironment.ts, flappyEvaluation.ts, constants/README.md
Open the browser demo and inspect the UI boundary browser-entry/browser-entry.ts or index.html browser-entry/README.md, flappy-evolution-worker/README.md
Understand the control problem itself environment/README.md simulation-shared/README.md, evaluation/README.md
Tune fairness, rollout policy, or fitness shaping evaluation/README.md trainer/README.md
Change worker playback or browser transport flappy-evolution-worker/README.md browser-entry/README.md
Understand the whole example as a system this README the module READMEs listed in Recommended Reading Order

Run The Example

Run training from Node

From the repo root:

npx ts-node examples/flappy_bird/trainFlappyBird.ts

You should see compact generation logs such as:

Training continues until you stop it with Ctrl+C.

Run the browser demo

From the repo root:

npm run start:local-server

Then open:

Important note:

The Core Idea In One Glance

The architectural rule is simple: keep simulation and evolution authoritative, keep the browser explanatory, and keep the worker boundary explicit.

flowchart LR
	subgraph NodePath[Node training path]
		TrainEntry["trainFlappyBird.ts\ntraining entrypoint"] --> Trainer["trainer/\nstaged evolutionary orchestration"]
		Trainer --> Evaluation["evaluation/\nshared-seed scoring"]
		Evaluation --> Environment["environment/\ndeterministic world stepping"]
		Environment --> Shared["simulation-shared/\nobservation semantics"]
		Shared --> Evaluation
	end

	subgraph BrowserPath[Browser playback path]
		BrowserShell["index.html\nlocal browser shell"] --> BrowserEntry["browser-entry/\nHUD, playback, network view"]
		BrowserEntry --> Worker["flappy-evolution-worker/\noff-thread evolution and playback"]
		Worker --> BrowserEntry
	end

	Constants["constants/\nshared knobs and palette"] -.-> Trainer
	Constants -.-> Environment
	Constants -.-> BrowserEntry

	PublicShelf["index.ts + facade files\nstable outside-facing shelves"] -.-> TrainEntry
	PublicShelf -.-> Evaluation
	PublicShelf -.-> Environment

	classDef boundary fill:#001522,stroke:#0fb5ff,color:#9fdcff,stroke-width:2px;
	classDef runtime fill:#03111f,stroke:#00e5ff,color:#d8f6ff,stroke-width:2px;
	classDef highlight fill:#2a1029,stroke:#ff4a8d,color:#ffd7e8,stroke-width:3px;

	class TrainEntry,Trainer,Evaluation,Environment,Shared,BrowserShell,BrowserEntry,Constants,PublicShelf boundary;
	class Worker runtime;
	class BrowserEntry highlight;

Read the diagram in two passes:

That split is the heart of the example. Once you understand it, the rest of the folder stops looking like many files and starts looking like a deliberate teaching system.

What Each Boundary Protects

`trainer/`: population policy

The trainer is the outer evolutionary loop. It decides how a generation is evaluated, how mutation pressure is scheduled, how reevaluation works, and what summary gets reported back out.

This boundary exists so the example's selection policy reads like policy instead of getting buried inside rollout mechanics or browser code.

`evaluation/`: fairness and score composition

Evaluation answers two different questions that should never be confused:

  1. What happened in one rollout?
  2. How stable is this genome across a shared batch of deterministic seeds?

That second question is the important one for evolution. The example deliberately prefers shared-seed batch evidence over one lucky episode.

`environment/`: the world itself

The environment owns bird state, pipe state, collision logic, pass credit, timeout behavior, and deterministic stepping. It is the simulation authority for one episode.

This boundary exists so the world can be reasoned about without needing to understand workers, DOM code, or training logs.

`simulation-shared/`: what the policy is actually seeing

This layer keeps observation semantics consistent across training, helper utilities, and browser playback-related tooling. If this boundary drifts, debugging becomes misleading because the policy may appear to be reacting to different worlds in different runtimes.

`browser-entry/`: the teaching surface

The browser entry boundary turns the evolved system into something a human can inspect. It owns the host UI, playback rendering, HUD updates, and network visualization.

It is intentionally not the source of truth for simulation. That keeps the UI responsive and keeps the educational surface honest.

`flappy-evolution-worker/`: off-thread authority

The worker owns evolution and playback simulation on the hot path. It transports packed snapshots and generation summaries back to the browser host rather than exposing live mutable runtime objects.

This boundary exists for both performance and clarity: message passing makes responsibility visible.

`constants/` and the facade files: stability shelves

The supporting boundaries matter too.

Two Execution Stories

The same example tells two different runtime stories depending on where you enter.

Training story

  1. trainFlappyBird.ts starts the Node-side trainer.
  2. trainer/README.md resolves staged rollout plans and mutation scheduling.
  3. evaluation/README.md scores each genome across shared seeds.
  4. environment/README.md advances one deterministic world frame by frame.
  5. The trainer ranks genomes, logs the generation summary, and evolves again.

Browser story

  1. index.html loads the browser shell.
  2. browser-entry/README.md creates the UI, HUD, and worker channel.
  3. flappy-evolution-worker/README.md evolves or simulates playback off-thread.
  4. The worker streams packed frame snapshots and generation summaries back to the main thread.
  5. The browser reconstructs frames, renders the flock, and visualizes the currently interesting network.

The important teaching point is that both stories depend on the same world and observation logic, but they do not collapse into the same runtime boundary.

The Most Important Design Bets

Several design decisions explain why the folder looks the way it does.

Shared-seed evaluation beats lucky-rollout selection

Genomes are compared on the same deterministic seeds. That makes fitness comparisons harsher, but also far more trustworthy. A network that wins under shared seeds is usually learning a policy, not just getting lucky.

Temporal observations give feed-forward networks local memory

The policy receives a 38-input observation assembled from current, previous, and two-frames-ago feature slices plus short action-memory signals. That gives a feed-forward controller short-horizon temporal context without requiring recurrent state.

Two outputs keep the action story simple

The action surface is intentionally narrow: no flap versus flap. A flap occurs when output[1] > output[0].

That simplicity is a teaching choice. It keeps the interesting complexity in state design, evaluation fairness, and runtime boundaries rather than in action decoding.

Decomposed fitness makes failure interpretable

Fitness is not one opaque number. It is composed from survival, pipe progress, dense shaping, and terminal shaping. That means poor performance can be inspected by channel instead of being treated like a mysterious scalar.

The browser is educational, not authoritative

The browser can render a full generation, highlight the current leader, and show connection sign, connection strength, disabled edges, and node bias. But it does not own simulation truth.

That distinction is what makes the example useful as a systems reference instead of only a visual demo.

Observation And Fitness Cheat Sheet

The observation vector focuses on control-relevant geometry rather than pixels. The policy sees a compressed description of the next decision, not a screenshot of the scene.

Signal family What it tells the policy Why it matters
Bird state vertical position and vertical velocity The network needs immediate kinematic context before deciding whether a flap is corrective or wasteful.
Next gap geometry distance to the next gap, gap bounds, and relative offset This is the primary near-term survival problem.
Second gap lookahead distance and delta to the following opening It discourages short-sighted behavior that solves one pipe but ruins the next one.
Clearance and urgency features how safe or dangerous the current approach is These features help shape timing near failure cases.
Action memory recent flap behavior It gives a feed-forward policy a small amount of temporal continuity.

Fitness then combines normalized channels with caps so one lucky dimension does not dominate selection:

The result is a reward surface that still encourages practical flying behavior, but remains debuggable when evolution stalls or overfits to one pattern.

If You Want To Change Something, Read This First

This is the shortest route to the right boundary when you are modifying the example.

Change goal Read first Why
Adjust gravity, pipe spacing, collision, or deterministic stepping environment/README.md That is where world truth lives.
Change what the network sees simulation-shared/README.md Observation semantics must stay aligned across training and playback-related helpers.
Change score shaping or fairness policy evaluation/README.md This boundary owns rollout scoring and shared-seed aggregation.
Change staged selection or mutation scheduling trainer/README.md This is the population-policy layer.
Change browser playback, HUD, or network inspection browser-entry/README.md The browser teaching surface lives here.
Change worker transport or off-thread playback behavior flappy-evolution-worker/README.md The worker owns hot-path simulation and packed snapshot transport.

Folder Map

The folder is split by responsibility, not by one giant "game" module.

If you want the cleanest ramp into the example, this order usually pays off:

  1. this README for the system-level mental model,
  2. trainer/README.md for the outer evolutionary loop,
  3. evaluation/README.md for fairness and score aggregation,
  4. environment/README.md for deterministic world mechanics,
  5. simulation-shared/README.md for observation semantics,
  6. browser-entry/README.md for the browser runtime and inspection surface,
  7. flappy-evolution-worker/README.md for worker protocol and playback transport.

If you only care about one slice:

Background Reading

This folder is readable from the code alone, but two external references genuinely help if you want the broader concepts behind the design:

Why Start Here

Many libraries look strong when explained as features. Fewer still look strong when asked to keep simulation deterministic, evaluation fair, browser playback responsive, and the resulting policy inspectable at the same time.

That is why this README matters. The goal is not merely to show that NeatapticTS can evolve a bird controller. The goal is to show what the library's engineering values look like when they have to coexist inside one runnable example.

If you want one folder that demonstrates the repo's broader direction for inspectable, reproducible, educational neuroevolution examples, this is the one.

Generated from source JSDoc • GitHub