diff --git a/src/app/evolve.config.js b/src/app/evolve.config.js
index b5837da72a48ced790cfc5b2cbcefea2735b00d2..96fb0b52b375639fadf0584b6cc8e2c73f2106e4 100644
--- a/src/app/evolve.config.js
+++ b/src/app/evolve.config.js
@@ -5,11 +5,14 @@ const root = path.resolve(__dirname);
 
 module.exports = {
     target: 'node',
-    entry: path.resolve(root, 'evolve.ts'),
+    entry: {
+        evolve: path.resolve(root, 'evolve/index.ts'),
+        'evolve.worker': path.resolve(root, 'evolve/worker.ts')
+    },
     mode: dev ? 'development' : 'production',
     output: {
         path: path.resolve(root, '..', 'dist'),
-        filename: 'evolve.js'
+        filename: '[name].js'
     },
     resolve: {
         extensions: ['.ts', '.js']
diff --git a/src/app/evolve.ts b/src/app/evolve.ts
deleted file mode 100644
index f53a461df39235354e17cd373a5f122407f159a9..0000000000000000000000000000000000000000
--- a/src/app/evolve.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import fs from 'fs';
-import path from 'path';
-
-import loader from '@assemblyscript/loader';
-
-import { AyakoAS } from '../ai';
-import { DummyActuator, DummyInputManager, DummyStorageManager } from '../dummies';
-import { GameManager } from '../game_manager';
-
-import { BENCHMARK_SEED } from './shared';
-import { RNG } from './rng';
-
-const POPULATION_SIZE = 64;
-const GENE_MIN = 0;
-const GENE_MAX = 5;
-const MUTATE_PRECISION = 100;
-const MUTATE_STRENGTH = 0.6;
-const MUTATE_LIKELY = 0.6;
-
-const rng = new RNG(Math.random)
-
-type Genes = [number, number, number, number, number, number, number, number, number];
-
-var generation = 1;
-
-function blank(): Genes {
-    return [0, 0, 0, 0, 0, 0, 0, 0, 0];
-}
-
-function mutateGene(gene: number): number {
-    return Math.max(GENE_MIN, Math.min(GENE_MAX, Math.floor((gene + rng.normal() * MUTATE_STRENGTH / Math.log(generation + 2)) * MUTATE_PRECISION) / MUTATE_PRECISION));
-}
-
-function mutateIndividual(genes: Genes): Genes {
-    const result = blank();
-    for (var i = 0; i < genes.length; i++) result[i] = (Math.random() > MUTATE_LIKELY) ? mutateGene(genes[i]) : genes[i];
-    return result;
-}
-
-const gameManager = new GameManager(4, new DummyInputManager, new DummyActuator, new DummyStorageManager,
-    () => BENCHMARK_SEED);
-var ayako = new AyakoAS(gameManager, new DummyInputManager,
-    loader.instantiateSync(fs.readFileSync(path.join(__dirname, '../../static/dist/ayako.rel.wasm'))));
-
-const baseGenes: Genes = [
-    +ayako.module.exports.WEIGHT_CLEANLY,
-    +ayako.module.exports.WEIGHT_GREEDY,
-    +ayako.module.exports.WEIGHT_CORNER,
-    +ayako.module.exports.WEIGHT_MONO3,
-    +ayako.module.exports.WEIGHT_CORNER_REWARD,
-    +ayako.module.exports.WEIGHT_CORNER_PENALTY,
-    +ayako.module.exports.WEIGHT_MONO3_REWARD,
-    +ayako.module.exports.WEIGHT_MONO3_PENALTY,
-    +ayako.module.exports.WEIGHT_IMPATIENCE
-];
-const GENE_COUNT = baseGenes.length;
-
-var population: Genes[] = [];
-
-function mean(...m: number[]): number {
-    return m.reduce((x, y) => x + y) / m.length;
-}
-
-function breed(p0: Genes, p1: Genes): Genes {
-    const offspring = blank();
-    for (var i = 0; i < GENE_COUNT; i++) offspring[i] = mean(p0[i], p1[i]);
-    return offspring;
-}
-
-function populate(template: Genes) {
-    for (var i = 0; i < POPULATION_SIZE; i++)
-        for (var j = 0; j < GENE_COUNT; j++)
-            population[i][j] = template[j];
-}
-
-function evaluateFitness(genes: Genes): number {
-    ayako.reset();
-    ayako.module.exports.loadWeights(...genes);
-    return ayako.module.exports.simulateToEnd(7);
-}
-
-function selectFittest(): [Genes, Genes] {
-    var fittest: Genes = null;
-    var runnerUp: Genes = null;
-    var best = 0;
-    for (var i = 0; i < POPULATION_SIZE; i++) {
-        const candidate = population[i];
-        console.log('evaluating fitness of', generation, i, '//', ...candidate);
-        const fitness = evaluateFitness(candidate);
-        if (fitness > best) {
-            runnerUp = fittest;
-            fittest = candidate;
-        }
-        console.log('score =', fitness);
-    }
-    return [fittest, runnerUp];
-}
-
-function doGeneration() {
-    console.log('mutating...');
-    for (var i = 0; i < POPULATION_SIZE; i++) population[i] = mutateIndividual(population[i]);
-    const fittest = selectFittest();
-    console.log('fittest', fittest[0]);
-    console.log('runner up', fittest[1]);
-    console.log('populating...');
-    const template = breed(...fittest);
-    populate(template);
-}
-
-for (var i = 0; i < POPULATION_SIZE; i++) population[i] = blank();
-populate(baseGenes);
-console.log(population);
-while (true) {
-    console.log('begin generation', generation);
-    doGeneration();
-    console.log('end of generation', generation);
-    generation++;
-}
\ No newline at end of file
diff --git a/src/app/evolve/index.ts b/src/app/evolve/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..367a3b92c927781ae025214f2fd92a7d5446cc9e
--- /dev/null
+++ b/src/app/evolve/index.ts
@@ -0,0 +1,115 @@
+import fs from 'fs';
+import path from 'path';
+
+import loader from '@assemblyscript/loader';
+
+import { AyakoASCore } from '../../ai';
+
+import { RNG } from './rng';
+
+import { Genes } from './types';
+import { EvolutionWorkerPool } from './worker';
+
+const POPULATION_SIZE = 128;
+const GENE_MIN = 0;
+const GENE_MAX = 5;
+const MUTATE_PRECISION = 100;
+const MUTATE_STRENGTH = 0.6;
+const MUTATE_LIKELY = 0.7;
+
+const rng = new RNG(Math.random)
+const pool = new EvolutionWorkerPool(7);
+const ayako = loader.instantiateSync<AyakoASCore>(fs.readFileSync(path.join(__dirname, '../../static/dist/ayako.rel.wasm')));
+const baseGenes: Genes = [
+    +ayako.exports.WEIGHT_CLEANLY,
+    +ayako.exports.WEIGHT_GREEDY,
+    +ayako.exports.WEIGHT_CORNER,
+    +ayako.exports.WEIGHT_MONO3,
+    +ayako.exports.WEIGHT_CORNER_REWARD,
+    +ayako.exports.WEIGHT_CORNER_PENALTY,
+    +ayako.exports.WEIGHT_MONO3_REWARD,
+    +ayako.exports.WEIGHT_MONO3_PENALTY,
+    +ayako.exports.WEIGHT_IMPATIENCE
+];
+const GENE_COUNT = baseGenes.length;
+
+var population: Genes[] = [];
+var generation = 1;
+
+function blankGenes(): Genes {
+    return [0, 0, 0, 0, 0, 0, 0, 0, 0];
+}
+
+function randomGenes(): Genes {
+    const v = blankGenes();
+    for (var i = 0; i < POPULATION_SIZE; i++)v[i] = rng.uniform() * GENE_MAX;
+    return v;
+}
+
+function mutateGene(gene: number): number {
+    return Math.max(GENE_MIN, Math.min(GENE_MAX, Math.floor((gene + rng.normal() * MUTATE_STRENGTH / Math.log(generation + 2)) * MUTATE_PRECISION) / MUTATE_PRECISION));
+}
+
+function mutateIndividual(genes: Genes): Genes {
+    const result = blankGenes();
+    for (var i = 0; i < genes.length; i++) result[i] = rng.uniform() > MUTATE_LIKELY ? mutateGene(genes[i]) : genes[i];
+    return result;
+}
+
+function weightedMean(d: number, r: number, m: number): number {
+    return (d * m + r) / (2 + m);
+}
+
+function breed(p0: Genes, p1: Genes): Genes {
+    const offspring = blankGenes();
+    for (var i = 0; i < GENE_COUNT; i++) offspring[i] = weightedMean(p0[i], p1[i], 2);
+    return offspring;
+}
+
+function populate(template: Genes) {
+    for (var i = 0; i < POPULATION_SIZE; i++)
+        for (var j = 0; j < GENE_COUNT; j++)
+            population[i][j] = template[j];
+}
+
+async function selectFittest(): Promise<[[Genes, number], [Genes, number]]> {
+    var fittest: Genes = null;
+    var runnerUp: Genes = null;
+    var best = 0, secondBest = 0;
+    const scores = await pool.evaluateMany(population);
+    console.log('evaluation complete');
+    for (var i = 0; i < POPULATION_SIZE; i++) {
+        if (scores[i] > best) {
+            runnerUp = fittest;
+            fittest = population[i];
+            secondBest = best;
+            best = scores[i];
+        }
+    }
+    return [[fittest, best], [runnerUp, secondBest]];
+}
+
+async function doGeneration() {
+    console.log('mutating...');
+    for (var i = 0; i < POPULATION_SIZE; i++) population[i] = mutateIndividual(population[i]);
+    console.log('evaluating fittest...');
+    const results = await selectFittest();
+    const fittest = results.map(x => x[0]) as [Genes, Genes];
+    const scores = results.map(x => x[1]);
+    console.log('fittest', fittest[0].join(','), scores[0]);
+    console.log('runner up', fittest[1].join(','), scores[1]);
+    console.log('populating...');
+    const template = breed(...fittest);
+    populate(template);
+}
+
+(async () => {
+    for (var i = 0; i < POPULATION_SIZE; i++) population[i] = blankGenes();
+    populate(baseGenes)
+    while (true) {
+        console.log('begin generation', generation);
+        await doGeneration();
+        console.log('end of generation', generation);
+        generation++;
+    }
+})();
\ No newline at end of file
diff --git a/src/app/rng.js b/src/app/evolve/rng.js
similarity index 100%
rename from src/app/rng.js
rename to src/app/evolve/rng.js
diff --git a/src/app/evolve/types.ts b/src/app/evolve/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1735864f6bc561c2e3b9559e80fd457d03578e0a
--- /dev/null
+++ b/src/app/evolve/types.ts
@@ -0,0 +1 @@
+export type Genes = [number, number, number, number, number, number, number, number, number];
\ No newline at end of file
diff --git a/src/app/evolve/worker.ts b/src/app/evolve/worker.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d6f1327a3581bedac4b7724385fa09586bcb9304
--- /dev/null
+++ b/src/app/evolve/worker.ts
@@ -0,0 +1,80 @@
+import fs from 'fs';
+import path from 'path';
+import { Worker, isMainThread, parentPort } from 'worker_threads';
+
+import loader from '@assemblyscript/loader';
+
+import { AyakoAS } from '../../ai';
+import { DummyActuator, DummyInputManager, DummyStorageManager } from '../../dummies';
+import { GameManager } from '../../game_manager';
+
+import { BENCHMARK_SEED } from '../shared';
+
+import { Genes } from './types';
+
+const gameManager = new GameManager(4, new DummyInputManager, new DummyActuator, new DummyStorageManager,
+    () => BENCHMARK_SEED);
+var ayako = new AyakoAS(gameManager, new DummyInputManager,
+    loader.instantiateSync(fs.readFileSync(path.join(__dirname, '../../static/dist/ayako.rel.wasm'))));
+
+function evaluateFitness(genes: Genes): number {
+    ayako.reset();
+    ayako.module.exports.loadWeights(...genes);
+    return ayako.module.exports.simulateToEnd(7);
+}
+
+if (!isMainThread) parentPort.on('message', (message: Genes) => {
+    const score = evaluateFitness(message);
+    parentPort.postMessage(score);
+});
+
+export class EvolutionWorker {
+
+    busy: boolean = false;
+    private worker: Worker;
+    private waiters: ((value?: any) => void)[] = [];
+
+    constructor() {
+        this.worker = new Worker(path.resolve(__dirname, 'evolve.worker.js'));
+    }
+
+    private setFree() {
+        if (this.waiters.length > 0) this.waiters.shift()();
+        else this.busy = false;
+    }
+
+    waitForFree(): Promise<void> {
+        if (this.busy) return new Promise(resolve => this.waiters.push(resolve));
+    }
+
+    async evaluateFitnessAsync(genes: Genes): Promise<number> {
+        if (this.busy) await this.waitForFree();
+        this.busy = true;
+        return await new Promise(resolve => {
+            this.worker.once('message', (score: number) => {
+                console.debug(genes.join(','), score);
+                this.setFree();
+                resolve(score);
+            });
+            this.worker.postMessage(genes);
+        });
+    }
+
+}
+
+export class EvolutionWorkerPool {
+
+    private pool: EvolutionWorker[] = [];
+
+    constructor(private size: number) {
+        for (var i = 0; i < size; i++) this.pool[i] = new EvolutionWorker();
+    }
+
+    evaluateMany(population: Genes[]): Promise<number[]> {
+        population = population.concat();
+        var results: Promise<number>[] = [];
+        for (var i = 0; i < population.length; i++) results.push(this.pool[i % this.size].evaluateFitnessAsync(population[i]));
+        return Promise.all(results);
+    }
+
+}
\ No newline at end of file
diff --git a/src/app/shared.ts b/src/app/shared.ts
index f8329ddf01cd67c5031027d22fed120ffcd852b9..cde13a773683493c72ee2a9fe46181da89aeee42 100644
--- a/src/app/shared.ts
+++ b/src/app/shared.ts
@@ -1 +1 @@
-export const BENCHMARK_SEED = '0fTAPVve';
\ No newline at end of file
+export const BENCHMARK_SEED = 'oUojMV5B';
\ No newline at end of file