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:
- How do you connect a NEAT population to a repeatable control problem instead of a one-off demo loop?
- How do you reduce lucky-rollout bias so evolution rewards robust behavior rather than fortunate seeds?
- How do you replay and inspect evolved behavior in the browser without moving simulation authority onto the main thread?
- 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:
gen=1 best=... pipes=... frames=...
Training continues until you stop it with Ctrl+C.
Run the browser demo
From the repo root:
npm run start:local-server
Then open:
http://localhost:8080/examples/flappy_bird/index.html
Important note:
index.htmlis a lightweight bundle-loading shell, not the source-of-truth browser runtime.- the real browser host starts in browser-entry/browser-entry.ts.
- the page loads example bundles from
docs/assets, so after code or documentation changes that affect the published example surface, runnpm run docsornpm run build:flappy-birdto refresh the browser-facing assets and generated docs.
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:
- the Node path evolves networks against a deterministic environment using shared-seed evaluation,
- the browser path explains and replays that story without taking control away from the worker-owned runtime.
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:
- What happened in one rollout?
- 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.
constants/keeps cross-cutting knobs and visual defaults centralized.index.ts,flappyEnvironment.ts, andflappyEvaluation.tsprovide stable outside-facing shelves for readers who want the example's public surfaces without learning the full folder layout first.
Two Execution Stories
The same example tells two different runtime stories depending on where you enter.
Training story
- trainFlappyBird.ts starts the Node-side trainer.
- trainer/README.md resolves staged rollout plans and mutation scheduling.
- evaluation/README.md scores each genome across shared seeds.
- environment/README.md advances one deterministic world frame by frame.
- The trainer ranks genomes, logs the generation summary, and evolves again.
Browser story
- index.html loads the browser shell.
- browser-entry/README.md creates the UI, HUD, and worker channel.
- flappy-evolution-worker/README.md evolves or simulates playback off-thread.
- The worker streams packed frame snapshots and generation summaries back to the main thread.
- 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:
- survival,
- pipe progress,
- dense shaping,
- terminal shaping.
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.
browser-entry/: main-thread browser runtime, HUD, playback renderer, viewport helpers, telemetry, and network visualization.constants/: shared visual, training, world, and playback constants.environment/: deterministic state, stepping, collision/progress rules, and environment-facing helpers.evaluation/: rollout execution, seed batching, and fitness aggregation.flappy-evolution-worker/: worker runtime for evolution, playback simulation, and snapshot transport.simulation-shared/: shared observation and simulation-facing feature logic reused across boundaries.trainer/: Node-side orchestration, staged evaluation plans, mutation scheduling, and generation logging.flappyEnvironment.ts: convenience facade for using the example as a control environment.flappyEvaluation.ts: convenience facade for using the example as a policy-evaluation surface.flappyEvolution.worker.ts: small worker bundle entry.trainFlappyBird.ts: direct Node entrypoint for starting training.index.html: local browser shell.index.ts: example-level public shelf.rng.ts: deterministic random utilities shared across runtime paths.
Recommended Reading Order
If you want the cleanest ramp into the example, this order usually pays off:
- this README for the system-level mental model,
- trainer/README.md for the outer evolutionary loop,
- evaluation/README.md for fairness and score aggregation,
- environment/README.md for deterministic world mechanics,
- simulation-shared/README.md for observation semantics,
- browser-entry/README.md for the browser runtime and inspection surface,
- flappy-evolution-worker/README.md for worker protocol and playback transport.
If you only care about one slice:
- training and fairness: start with
trainer/andevaluation/, - world mechanics and control semantics: start with
environment/andsimulation-shared/, - browser playback and UI boundaries: start with
browser-entry/andflappy-evolution-worker/.
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:
- Kenneth O. Stanley and Risto Miikkulainen, "Evolving Neural Networks through Augmenting Topologies", Evolutionary Computation 10(2), 2002. This is the canonical NEAT paper and the best conceptual backdrop for why topology and weights co-evolve.
- Wikipedia contributors, "Message passing", Wikipedia, The Free Encyclopedia. This is useful background for understanding why the browser and worker communicate through an explicit protocol instead of sharing runtime authority.
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.