flappy-evolution-worker
Off-thread evolution and playback authority for the Flappy Bird browser demo.
This worker boundary exists to keep the browser honest. The main thread owns explanation, HUD rendering, and network inspection, while the worker owns the hot path: evolving generations, materializing playback state, advancing the simulation, and packaging compact snapshots back to the host.
That separation is doing two jobs at once. It protects the browser from heavy simulation work, and it turns responsibility into something a reader can see directly because every cross-thread handoff must become an explicit typed message.
Read this folder as the protocol chapter between the browser host and the deterministic runtime that actually evolves and replays the flock. The important design question is not merely "how do Web Workers run code?" It is "which side should own each piece of truth when evolution, playback, and inspection all need the same population?"
In this example, the answer is deliberate:
- the host owns controls, HUD state, and network visualization,
- the worker owns generation requests, playback stepping, and winner selection,
- packed snapshots are the narrow bridge between those two worlds.
What This Folder Is Trying To Teach
The worker chapter is organized around four reader questions:
- How does the browser request evolution work without becoming the simulation authority?
- How does one evolved population become a replayable playback session?
- Why are snapshots packed into typed arrays instead of posted as nested render objects?
- Where should you read next when the protocol is clear but one runtime step still feels opaque?
Core Worker Map
flowchart LR
Host["browser-entry/\nhost UI and controls"] --> Protocol["protocol service\nlegal message transitions"]
Protocol --> Runtime["runtime service\nworker-local NEAT controller"]
Runtime --> Evolution["evolution service\nadvance one generation"]
Runtime --> Playback["playback service\nmaterialize and step population"]
Playback --> Snapshot["snapshot utils\npacked typed-array transport"]
Snapshot --> Host
WarmStart["warm-start service\ngeneration zero bootstrap"] -.-> Evolution
Types["worker types\nmessage and DTO contracts"] -.-> Protocol
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 Host,Protocol,Types boundary;
class Runtime,Evolution,Playback,Snapshot runtime;
class Snapshot highlight;Read the diagram left to right. The host is allowed to request work, but it never takes ownership of the worker's mutable simulation state. The worker can then optimize for determinism and throughput while the browser optimizes for explanation.
Choose Your Route
- Start with
flappy-evolution-worker.tsif you want the high-level message lifecycle. - Read
flappy-evolution-worker.protocol.service.tsnext if you want the legal sequencing rules. - Read
flappy-evolution-worker.runtime.service.tsif you want the worker's NEAT runtime setup. - Read
flappy-evolution-worker.playback.service.tsandflappy-evolution-worker.simulation.frame.service.tsif you want the hot playback path. - Read
flappy-evolution-worker.snapshot.utils.tsif you want the typed-array transport story.
Minimal host-side sketch:
worker.postMessage({
type: 'init',
payload: { populationSize: 50, elitismCount: 10, rngSeed: 12345 },
});
worker.postMessage({ type: 'request-generation' });
worker.postMessage({
type: 'start-playback',
payload: { visibleWorldWidthPx: 1280, visibleWorldHeightPx: 720 },
});
worker.postMessage({
type: 'request-playback-step',
payload: {
requestId: 1,
simulationSteps: 2,
visibleWorldWidthPx: 1280,
visibleWorldHeightPx: 720,
},
});
If you want background reading before the symbol shelf, the MDN Web Workers guide is the fastest practical reference for why this example pushes both evolution and playback off the main thread.
Parallel evaluation ownership
This worker chapter now sits on top of the public turnkey helper ladder instead of proving each worker primitive in demo-local code. Flappy uses the shared library helpers for capability detection, transport auto-selection, browser worker URL resolution, bounded inference pools, ordered batch evaluation, and the boolean-first NEAT population helper.
The worker still owns the parts that are example-specific rather than transport-generic: deciding which architecture profiles should opt into parallel evaluation, preparing Flappy-specific rollout payloads, and turning worker-local results into the compact generation-ready or playback-step messages the host consumes.
flappy-evolution-worker/flappy-evolution-worker.ts
beginWorkerGenerationRequest
beginWorkerGenerationRequest(
workerMutableRuntimeState: WorkerMutableRuntimeState,
): void
Begins one asynchronous generation request and captures failures.
This is intentionally fire-and-forget from the protocol perspective. The
actual completion signal is the later generation-ready or error message
posted back to the host.
Parameters:
workerMutableRuntimeState- Mutable worker runtime state.
Returns: Nothing.
beginWorkerInitialization
beginWorkerInitialization(
workerMutableRuntimeState: WorkerMutableRuntimeState,
initPayload: { architectureProfileId?: ExampleArchitectureProfileId | undefined; championNetworkJson?: SerializedNetwork | undefined; populationSize: number; elitismCount: number; rngSeed: number; },
): void
Begins worker initialization and captures asynchronous failures.
The worker retains the initialization promise so later generation requests can await setup completion instead of racing against it.
Parameters:
workerMutableRuntimeState- Mutable worker runtime state.initPayload- Initialization payload.
Returns: Nothing.
beginWorkerPlayback
beginWorkerPlayback(
workerMutableRuntimeState: WorkerMutableRuntimeState,
payload: { visibleWorldWidthPx: number; visibleWorldHeightPx: number; },
): Promise<void>
Begins a new playback session from the current evolved population.
A playback session is a deterministic simulation snapshot seeded from the current population. Each new session resets playback RNG and world state so the host can replay generations cleanly.
Parameters:
workerMutableRuntimeState- Mutable worker runtime state.payload- Playback start payload.
Returns: Nothing.
createWorkerEvaluationPoolIfSupported
createWorkerEvaluationPoolIfSupported(
initPayload: { architectureProfileId?: ExampleArchitectureProfileId | undefined; championNetworkJson?: SerializedNetwork | undefined; populationSize: number; elitismCount: number; rngSeed: number; },
): FlappyEvaluationWorkerPool | undefined
Creates the optional shared-memory evaluation pool for recurrent browser profiles.
The pool is useful only when the worker can honestly spawn the emitted shared
inference worker with SharedArrayBuffer transport. Otherwise the runtime
keeps the worker-local direct evaluator, which avoids a chatty nested-worker
path on ordinary local servers without COOP/COEP isolation.
Parameters:
initPayload- Worker initialization payload with the selected profile.
Returns: Shared-memory pool when the host can support it; otherwise undefined.
createWorkerMessageHandler
createWorkerMessageHandler(
workerMutableRuntimeState: WorkerMutableRuntimeState,
): (event: MessageEvent<WorkerRequestMessage>) => void
Creates the top-level worker message handler.
The returned function is intentionally thin. All protocol decisions are delegated to the router service so the worker entrypoint stays readable as a high-level orchestration module.
Parameters:
workerMutableRuntimeState- Mutable worker runtime state.
Returns: Worker message handler.
Example:
self.onmessage = createWorkerMessageHandler(workerMutableRuntimeState);
createWorkerMutableRuntimeState
createWorkerMutableRuntimeState(): WorkerMutableRuntimeState
Creates the mutable worker runtime state container.
Educational note: The worker keeps one small mutable state bag instead of scattering globals. That makes the protocol flow easier to explain and lets the entrypoint pass a single dependency object through the orchestration helpers.
Returns: Mutable worker runtime state.
createWorkerProtocolHandlers
createWorkerProtocolHandlers(
workerMutableRuntimeState: WorkerMutableRuntimeState,
): { markStopped: () => void; beginInitialization: (payload: { architectureProfileId?: ExampleArchitectureProfileId | undefined; championNetworkJson?: SerializedNetwork | undefined; populationSize: number; elitismCount: number; rngSeed: number; }) => void; beginGenerationRequest: () => void; hasPopulation: () => boolean; startPlayback: (payload: { visibleWorldWidthPx: number; visibleWorldHeightPx: number; }) => void; hasPlaybackState: () => boolean; processPlaybackStep: (payload: { requestId: number; simulationSteps: number; visibleWorldWidthPx: number; visibleWorldHeightPx: number; }) => void; postWorkerMessage: typeof postWorkerMessage; }
Creates protocol handlers bound to the mutable worker runtime state.
This helper is the bridge between the pure protocol router and the impure worker runtime. Each callback closes over the same mutable state bag so the protocol layer can remain small and declarative.
Parameters:
workerMutableRuntimeState- Mutable worker runtime state.
Returns: Protocol handler bundle.
evolveAndPublishGeneration
evolveAndPublishGeneration(
workerMutableRuntimeState: WorkerMutableRuntimeState,
): Promise<void>
Evolves one generation and publishes the best-network summary message.
Educational note: This method is the orchestration seam between evolutionary search and browser rendering: it runs evolution, snapshots the population, and emits a compact payload for UI state updates.
Returns: Promise resolved after generation payload is posted.
initializeRuntime
initializeRuntime(
workerMutableRuntimeState: WorkerMutableRuntimeState,
initPayload: { architectureProfileId?: ExampleArchitectureProfileId | undefined; championNetworkJson?: SerializedNetwork | undefined; populationSize: number; elitismCount: number; rngSeed: number; },
): Promise<void>
Initializes the worker-local NEAT runtime used by browser evolution playback.
Educational note: The runtime is configured once with deterministic RNG state and a lightweight early-termination fitness rollout. Keeping this setup centralized helps ensure reproducibility between runs and keeps host<->worker contracts simple.
This function only prepares the evolutionary controller. It does not start playback and it does not evolve a generation yet; those remain separate protocol steps so the host can control them explicitly.
Parameters:
initPayload- Initialization values from the browser host.
Returns: Promise resolved when runtime setup is complete.
logWorkerEvaluationPoolFallback
logWorkerEvaluationPoolFallback(
architectureProfileId: ExampleArchitectureProfileId | undefined,
reasons: readonly string[],
): void
Logs why recurrent evaluation stayed on the direct worker-local evaluator.
Parameters:
architectureProfileId- Selected recurrent profile id.reasons- Capability probe explanations for unavailable transports.
Returns: Nothing.
postWorkerMessage
postWorkerMessage(
workerMessage: WorkerResponseMessage,
transferList: Transferable[] | undefined,
): void
Posts a typed message from worker to host.
This is the narrowest possible transport helper: all message construction is done elsewhere so the README can point to one stable worker-to-host boundary.
Parameters:
workerMessage- Outbound worker response payload.transferList- Optional transferable buffers moved with the payload.
Returns: Nothing.
postWorkerRuntimeStatus
postWorkerRuntimeStatus(
payload: { phase: WorkerRuntimeStatusPhase; statusText: string; detail?: string | undefined; },
): void
Posts an informational runtime-status update from worker to browser host.
Parameters:
payload- Phase and display text for the HUD status row.
Returns: Nothing.
processWorkerPlaybackStepRequest
processWorkerPlaybackStepRequest(
workerMutableRuntimeState: WorkerMutableRuntimeState,
playbackStepPayload: { requestId: number; simulationSteps: number; visibleWorldWidthPx: number; visibleWorldHeightPx: number; },
): Promise<void>
Advances playback by a host-requested number of simulation steps.
Educational note: The browser host can request multiple simulation steps per RAF to trade visual smoothness against throughput. This function keeps that loop deterministic and emits one compact snapshot payload per request.
Importantly, the worker remains authoritative for deciding when the run is over and which bird should be treated as the playback winner.
Parameters:
playbackStepPayload- Host-selected simulation-step budget and viewport.
Returns: Nothing.
resolveWorkerEvaluationRuntimeStatusPayload
resolveWorkerEvaluationRuntimeStatusPayload(
evaluationWorkerPool: FlappyEvaluationWorkerPool | undefined,
): { phase: WorkerRuntimeStatusPhase; statusText: string; detail?: string | undefined; }
Resolves the status payload for the active recurrent evaluation transport.
Parameters:
evaluationWorkerPool- Optional shared-memory evaluation pool.
Returns: Runtime-status payload for the HUD.
flappy-evolution-worker/flappy-evolution-worker.types.ts
SerializedNetwork
Loose JSON-compatible network payload used by worker messages.
The worker never posts live Network instances back to the browser host.
The transferable inference payload now owns playback-friendly transport,
while this JSON bridge remains only for the browser-side network-view cache.
WorkerErrorMessage
Worker error response message.
Errors are normalized into a display-safe string so the host UI can surface failures without depending on worker-specific exception classes.
WorkerFrameBirdSnapshot
Render-only bird snapshot DTO posted to host.
This shape is useful conceptually, but the current transport uses the packed typed-array variant for lower allocation and transfer cost.
WorkerFramePipeSnapshot
Render-only pipe snapshot DTO posted to host.
Like WorkerFrameBirdSnapshot, this documents the logical payload shape even
though the worker currently sends the packed transport form.
WorkerGenerationReadyMessage
Worker generation-ready response message.
The browser host uses this message to refresh HUD state, render the current best network visualization, and start playback for the current population. Generation zero can be a startup release after warm-start rather than a full post-selection NEAT generation.
WorkerHeuristicObservationFeatures
Structured features used by heuristic generation-0 teacher policy.
The warm-start service reuses the same high-level observation semantics as the real policy inference path, which keeps the heuristic teacher aligned with the features evolved networks will later see.
WorkerInitMessage
Worker init request message.
This is the first message the host should send. It seeds deterministic RNG state and configures the worker-local NEAT runtime.
WorkerPackedPlaybackBirdSnapshot
Packed typed-array payload for playback bird snapshot transport.
The host can reconstruct renderer-friendly bird views from these arrays while the worker keeps the authoritative mutable simulation objects private.
WorkerPackedPlaybackPipeSnapshot
Packed typed-array payload for playback pipe snapshot transport.
Packing the per-pipe fields into column-oriented typed arrays makes the browser/worker boundary cheaper than sending large arrays of object literals on every animation frame.
WorkerPlaybackFrameSnapshot
Full frame snapshot payload posted to host.
Educational note:
packed-v1 is a transport contract, not a rendering primitive. The versioned
format string gives the browser host a stable way to decode snapshots even if
the worker later gains additional packed fields or alternate transport modes.
Example:
const message = {
type: 'playback-step',
payload: {
requestId: 7,
snapshot,
done: false,
},
};
WorkerPlaybackState
Mutable simulation state stored between worker playback requests.
A start-playback message creates this state once, and each
request-playback-step message advances it by a host-selected number of
simulation steps.
WorkerPlaybackStepMessage
Worker playback-step response message.
The message carries the packed frame snapshot plus optional instrumentation and end-of-run summary statistics when the whole simulated population has been eliminated.
The split between per-frame snapshot data and end-of-run summary fields keeps the hot path compact while still giving the host enough telemetry to update HUD metrics when a playback session completes.
WorkerPopulationBird
Mutable bird state tracked by the worker playback simulation.
Educational note:
Each bird keeps both physics state and policy state. The
observationMemoryState field stays on the bird so worker playback shares
the same control-state shape as evaluation and browser helpers. The current
controller input does not read external history, but the aligned state shelf
keeps future opt-in experiments from forking the runtime contracts.
WorkerPopulationPipe
Mutable pipe state tracked by the worker playback simulation.
These objects exist only inside the worker runtime. The host later receives a packed snapshot derived from them rather than these live mutable records.
WorkerRequestGenerationMessage
Worker request asking for the next playable generation payload.
The first response may release the bounded generation-zero warm-start population before a full recurrent evolution pass. Later responses publish normally evolved populations.
WorkerRequestMessage
Union of inbound worker request messages.
Reading this union top-to-bottom is the quickest way to understand the worker protocol: initialize, evolve, start playback, step playback, then stop.
WorkerRequestPlaybackStepMessage
Worker request asking to advance playback by N simulation steps.
The host typically sends this once per animation frame and chooses
simulationSteps based on how much simulation throughput it wants relative to
rendering smoothness.
WorkerResponseMessage
Union of outbound worker response messages.
Together with WorkerRequestMessage, this forms the full host/worker
protocol contract for the demo.
WorkerRuntimeStatusMessage
Worker runtime status response message.
Status messages are informational and never complete a request. The browser listens for them beside generation/playback responses so long recurrent waits can explain whether work is initializing, evolving, playing back, or using a direct-evaluation fallback.
WorkerRuntimeStatusPhase
Worker phase labels surfaced to the browser HUD during long-running work.
WorkerStartPlaybackMessage
Worker request asking to initialize playback state.
This materializes the mutable world state for the current evolved population. After this message succeeds, the host can begin issuing playback-step requests.
WorkerStopMessage
Worker stop request message.
This is a cooperative shutdown signal. Long-running worker flows can observe the stopped flag and fail fast instead of continuing work the UI no longer cares about.
flappy-evolution-worker/flappy-evolution-worker.runtime.service.ts
buildWorkerSharedRolloutSeedBatch
buildWorkerSharedRolloutSeedBatch(
workerInitSeed: number,
generation: number,
sharedRolloutSeedCount: number,
): number[]
Builds the deterministic rollout seed batch used by the pipe-first worker objective.
Parameters:
workerInitSeed- Deterministic worker seed.
Returns: Shared rollout seed batch.
createInitializedWorkerRuntime
createInitializedWorkerRuntime(
initPayload: { architectureProfileId?: ExampleArchitectureProfileId | undefined; championNetworkJson?: SerializedNetwork | undefined; populationSize: number; elitismCount: number; rngSeed: number; },
workerRuntimeDependencies: WorkerRuntimeDependencies,
): default
Creates and configures the worker-local NEAT runtime used by browser evolution playback.
Educational note: The browser worker reuses the same core NeatapticTS runtime as the Node-side trainer, but trims configuration down to the pieces needed for an interactive example: deterministic seeding, feed-forward mutation policy, and a fitness function that favors quick browser-visible iteration.
The resulting runtime is both the evolution engine and the source of the population that later playback requests visualize.
For background reading, the Wikipedia article on "Neuroevolution of augmenting topologies" is a useful overview of the family of ideas this demo is exercising, even though the repository implements its own detailed runtime behavior and modern extensions.
Parameters:
initPayload- Initialization values from the browser host.
Returns: Initialized NEAT runtime.
Example:
const neatRuntime = createInitializedWorkerRuntime({
populationSize: 50,
elitismCount: 10,
rngSeed: 12345,
});
createWorkerFitnessEvaluator
createWorkerFitnessEvaluator(
architectureProfileId: NonNullable<ExampleArchitectureProfileId | undefined>,
workerInitSeed: number,
resolveCurrentGeneration: () => number,
workerRuntimeDependencies: WorkerRuntimeDependencies,
): NeatFitnessFunction
Builds the worker fitness evaluator for the selected architecture profile.
NARX, GRU, and LSTM benefit from a slightly stricter browser objective because the tiny interactive population is otherwise too willing to overfit one lucky rollout and stall at a zero-pipe local optimum.
Parameters:
architectureProfileId- Resolved worker architecture profile id.workerInitSeed- Deterministic worker seed.
Returns: Worker-local scalar fitness function.
logWorkerFitnessTransportMode
logWorkerFitnessTransportMode(
architectureProfileId: NonNullable<ExampleArchitectureProfileId | undefined>,
usesPipeFirstSharedSeeds: boolean,
usesParallelWorkerPool: boolean,
): void
Logs the worker-side fitness transport selection.
Parameters:
architectureProfileId- Resolved worker architecture profile id.usesPipeFirstSharedSeeds- Whether the profile uses the shared-seed aggregate evaluator.inferenceChannelWorkerUrl- Nested worker bundle URL when persistent channels are available.
Returns: Nothing.
resolveFirstSharedRolloutSeedBatch
resolveFirstSharedRolloutSeedBatch(
sharedRolloutSeeds: readonly number[],
): number[]
Resolves the cheap first-seed batch used before full recurrent scoring.
Parameters:
sharedRolloutSeeds- Full deterministic seed batch for the generation.
Returns: One-seed batch used for the progressive gate.
resolveRequiredFirstSeedAggregate
resolveRequiredFirstSeedAggregate(
aggregateByGenome: ReadonlyMap<default, FlappySeedBatchEvaluation>,
genome: default,
): FlappySeedBatchEvaluation
Resolves the required first-seed aggregate for a genome.
Parameters:
aggregateByGenome- First-seed aggregate map.genome- Genome whose evidence should exist.
Returns: Aggregate evaluation for the genome.
resolveWorkerPipeFirstEvaluationPlan
resolveWorkerPipeFirstEvaluationPlan(
architectureProfileId: NonNullable<ExampleArchitectureProfileId | undefined>,
): WorkerPipeFirstEvaluationPlan
Resolves the recurrent worker evaluation plan for one browser profile.
LSTM gets a slightly broader shared-seed batch than the other recurrent profiles because the heavier gated controller was still regressing after generation-zero warm-start when browser selection only saw one static lane.
Parameters:
architectureProfileId- Resolved worker architecture profile id.
Returns: Shared-seed batch size for worker fitness.
resolveWorkerPipeFirstRolloutOptions
resolveWorkerPipeFirstRolloutOptions(): { enableEarlyTermination: true; maxFrames: number; normalizeFitness: true; pipeProgressTarget: number; }
Resolves shared rollout options for the pipe-first browser objective.
Returns: Rollout options used by recurrent worker scoring.
resolveWorkerSeedNetwork
resolveWorkerSeedNetwork(
initPayload: { architectureProfileId?: ExampleArchitectureProfileId | undefined; championNetworkJson?: SerializedNetwork | undefined; populationSize: number; elitismCount: number; rngSeed: number; },
architectureProfileId: NonNullable<ExampleArchitectureProfileId | undefined>,
): default
Resolves the worker seed network from a saved champion override or the shared profile template.
Parameters:
initPayload- Initialization values from the browser host.architectureProfileId- Resolved architecture profile id.
Returns: Seed network for the worker-local NEAT runtime.
scorePipeFirstWorkerAggregateEvaluation
scorePipeFirstWorkerAggregateEvaluation(
aggregateEvaluation: FlappySeedBatchEvaluation,
): number
Scores one aggregate with a pipe-first browser selection scalar.
The interactive worker population is tiny, so escaping the first-pipe local optimum matters more than preserving the exact trainer ranking stack. This scalar therefore promotes mean pipe progress first, then uses survival and stability as tie-breakers inside the same shared-seed batch.
Parameters:
aggregateEvaluation- Shared-seed evaluation evidence.
Returns: Scalar fitness consumed by the browser worker NEAT loop.
shouldSpendFullWorkerSeedBatch
shouldSpendFullWorkerSeedBatch(
aggregateEvaluation: FlappySeedBatchEvaluation,
): boolean
Resolves whether one genome should receive the full recurrent seed batch.
Parameters:
aggregateEvaluation- First-seed evidence for one genome.
Returns: True when the genome showed enough pipe progress to justify full scoring.
flappy-evolution-worker/flappy-evolution-worker.protocol.service.ts
routeWorkerProtocolMessage
routeWorkerProtocolMessage(
workerMessage: WorkerRequestMessage,
handlers: WorkerProtocolHandlers,
): void
Routes one inbound worker request message to the corresponding runtime action.
Educational note: This router is the protocol gatekeeper for the worker. It enforces the two important sequencing rules in the demo:
- playback requires a previously evolved population,
- playback stepping requires an active playback session.
In practice this acts like a tiny finite-state machine. If you want a quick conceptual refresher, the Wikipedia article on "finite-state machine" maps well onto the worker's init -> evolve -> start playback -> step playback flow.
Parameters:
workerMessage- Inbound worker request payload.handlers- Runtime action callbacks and state probes.
Returns: Nothing.
Example:
routeWorkerProtocolMessage(
{ type: 'request-generation' },
workerProtocolHandlers,
);
WorkerProtocolHandlers
Callback bundle used by worker protocol routing.
Each callback corresponds to one legal transition in the worker message
protocol. Keeping the router dependent on this narrow interface makes the
protocol easy to read in generated docs and easy to test independently from
the worker-global self.onmessage hook.
flappy-evolution-worker/flappy-evolution-worker.evolution.service.ts
buildGenerationReadyMessage
buildGenerationReadyMessage(
options: { architectureProfileId: ExampleArchitectureProfileId; generation: number; bestNetwork: default; population: default[]; },
): WorkerGenerationReadyMessage
Builds the generation-ready worker response from a population snapshot.
Parameters:
options- Generation metadata plus selected best network and population.
Returns: Generation-ready worker response payload.
evolveAndBuildGenerationReadyMessage
evolveAndBuildGenerationReadyMessage(
options: WorkerEvolutionServiceOptions,
): Promise<WorkerGenerationReadyMessage>
Creates the next compact generation-ready response payload.
Educational note:
The first browser-visible population should not wait for a full recurrent
selection batch or optional warm-start assist. When the caller opts in,
generation zero is released immediately so playback can begin promptly. Later
requests run the bounded warm-start assist, the normal NEAT evolve() pass,
and emit the same compact summary shape: generation index, best fitness,
transferable inference payloads for playback, and the temporary JSON
visualization bridge used by the host network panel.
Parameters:
options- Evolution dependencies and runtime state accessors.
Returns: Generation-ready worker response payload.
Example:
const generationMessage = await evolveAndBuildGenerationReadyMessage({
initializationPromise,
neatRuntime,
isStopped: () => false,
warmStartGenerationZeroIfNeeded,
setCurrentPopulation,
});
resolveBestNetworkFromPopulation
resolveBestNetworkFromPopulation(
population: readonly default[],
): default
Chooses the best network from a population snapshot using available scores.
Parameters:
population- Population snapshot to scan.
Returns: Highest-scored network, falling back to the first network when scores are equal.
resolveGenerationReadyMessageTransferList
resolveGenerationReadyMessageTransferList(
workerMessage: WorkerGenerationReadyMessage,
): ArrayBuffer[]
Collect the transferable buffers owned by one generation-ready response payload.
The transfer list intentionally includes only the typed-array inference payloads. The temporary JSON visualization bridge remains structured-clone data so the browser can continue rebuilding network-view models separately.
Parameters:
workerMessage- Generation-ready worker response payload.
Returns: Transfer list for postMessage(...).
resolveRuntimePopulation
resolveRuntimePopulation(
runtimeNeat: { population?: default[] | undefined; },
fallbackNetwork: default | undefined,
): default[]
Resolves the current runtime population with an optional best-network fallback.
Parameters:
runtimeNeat- Runtime population holder.fallbackNetwork- Best network returned by an evolution pass.
Returns: Non-empty population when one is available.
runBestEffortWarmStart
runBestEffortWarmStart(
warmStartGenerationZeroIfNeeded: (neatController: default) => Promise<void>,
neatRuntime: default,
): Promise<void>
Runs generation-zero warm-start as an optional assist before regular evolution.
Parameters:
warmStartGenerationZeroIfNeeded- Warm-start callback for the active runtime.neatRuntime- Runtime that should evolve even when warm-start fails.
Returns: Promise resolved after warm-start succeeds or is skipped.
shouldPublishStartupPopulationBeforeEvolution
shouldPublishStartupPopulationBeforeEvolution(
generation: number,
population: readonly default[],
publishStartupPopulationBeforeFirstEvolution: boolean | undefined,
): boolean
Resolves whether the worker should release generation zero before evolving.
Parameters:
generation- Current NEAT generation index.population- Current worker population snapshot.publishStartupPopulationBeforeFirstEvolution- Caller startup-release flag.
Returns: True when generation zero can be published immediately for playback.
WorkerEvolutionServiceOptions
Dependencies required to evolve one generation and prepare host payload output.
Educational note: This interface isolates the evolution step from the worker entrypoint. That makes the README easier to follow: the entrypoint owns protocol orchestration, while this service owns one well-defined "run generation -> publish summary" slice of behavior.
flappy-evolution-worker/flappy-evolution-worker.playback.service.ts
beginWorkerPlaybackSession
beginWorkerPlaybackSession(
options: { currentPopulation: default[]; payload: { visibleWorldWidthPx: number; visibleWorldHeightPx: number; }; createPopulationRenderState: (networks: default[], rng: FlappyRng, initialVisibleWorldWidthPx: number, initialVisibleWorldHeightPx: number, channelWorkerUrl?: string | undefined) => WorkerPlaybackState; channelWorkerUrl?: string | undefined; },
): { currentPlaybackState: WorkerPlaybackState; currentPlaybackRng: FlappyRng; playbackWinnerIndex: number; }
Creates a fresh worker playback session state from the current evolved population.
Educational note: Evolution and playback are intentionally separated. Evolution produces a new population, then playback freezes that population into a deterministic simulation state that the host can step frame-by-frame for rendering.
Parameters:
currentPopulation- Current evolved population.payload- Playback start viewport payload.createPopulationRenderState- Callback that builds initial simulation state.
Returns: Playback runtime state and deterministic RNG.
Example:
const session = beginWorkerPlaybackSession({
currentPopulation,
payload: { visibleWorldWidthPx: 1280, visibleWorldHeightPx: 720 },
createPopulationRenderState,
});
maybeDownshiftSuccessfulBrowserPopulation
maybeDownshiftSuccessfulBrowserPopulation(
neatRuntime: default | undefined,
winnerPipesPassed: number,
): void
Downshifts the worker's future browser population budget after a successful playback run.
The current generation has already been evaluated, so the savings apply to future generations only. This keeps the demo responsive once an architecture has already demonstrated that it can clear the live pipe target.
Parameters:
neatRuntime- Worker-local NEAT runtime.winnerPipesPassed- Winning playback pipe count for the completed generation.
Returns: Nothing.
processWorkerPlaybackStep
processWorkerPlaybackStep(
options: { playbackStepPayload: { requestId: number; simulationSteps: number; visibleWorldWidthPx: number; visibleWorldHeightPx: number; }; currentPlaybackState: WorkerPlaybackState; currentPlaybackRng: FlappyRng; currentPopulation: default[]; neatRuntime: default | undefined; stepPopulationFrame: (renderState: WorkerPlaybackState, rng: FlappyRng, difficultyProfile: SharedDifficultyProfile) => Promise<number>; createPlaybackSnapshot: (playbackState: WorkerPlaybackState) => WorkerPlaybackFrameSnapshot; resolvePlaybackSnapshotTransferList: (snapshot: WorkerPlaybackFrameSnapshot) => Transferable[]; postWorkerMessage: (workerMessage: WorkerResponseMessage, transferList?: Transferable[] | undefined) => void; },
): Promise<{ currentPlaybackState: WorkerPlaybackState | undefined; currentPlaybackRng: FlappyRng | undefined; currentPopulation: default[]; playbackWinnerIndex: number; }>
Processes one worker playback-step request including completion/finalization logic.
Educational note: One playback request may advance multiple simulation steps. This lets the host trade visual smoothness against throughput while keeping the worker in control of simulation correctness, winner selection, and packed snapshot publishing.
Parameters:
options- Playback step dependencies and mutable runtime state.
Returns: Updated playback runtime state after processing this step.
flappy-evolution-worker/flappy-evolution-worker.snapshot.utils.ts
canReuseSharedSnapshotBuffers
canReuseSharedSnapshotBuffers(): boolean
Resolves whether this host can reuse shared snapshot buffers safely.
Returns: True when SharedArrayBuffer snapshot storage is available.
createFloat32SnapshotArray
createFloat32SnapshotArray(
elementCount: number,
useSharedBuffer: boolean,
): Float32Array<ArrayBufferLike>
Creates one packed float column for snapshot transport.
Parameters:
elementCount- Number of elements in the column.useSharedBuffer- Whether to allocate reusable shared memory.
Returns: Float32 snapshot column.
createUint32SnapshotArray
createUint32SnapshotArray(
elementCount: number,
useSharedBuffer: boolean,
): Uint32Array<ArrayBufferLike>
Creates one packed uint32 column for snapshot transport.
Parameters:
elementCount- Number of elements in the column.useSharedBuffer- Whether to allocate reusable shared memory.
Returns: Uint32 snapshot column.
createUint8SnapshotArray
createUint8SnapshotArray(
elementCount: number,
useSharedBuffer: boolean,
): Uint8Array<ArrayBufferLike>
Creates one packed uint8 column for snapshot transport.
Parameters:
elementCount- Number of elements in the column.useSharedBuffer- Whether to allocate reusable shared memory.
Returns: Uint8 snapshot column.
createWorkerPlaybackSnapshot
createWorkerPlaybackSnapshot(
playbackState: WorkerPlaybackState,
): WorkerPlaybackFrameSnapshot
Creates a serializable snapshot of current playback state.
Workers should send only structured-clone-safe payloads. This helper strips runtime-only references (e.g., network instances, sets) and keeps only renderer-relevant fields.
Educational note: The snapshot is intentionally column-oriented. By packing values into typed arrays, the worker can transfer large bird populations to the host with much lower overhead than a per-frame array of nested objects.
This is a small example of a structure-of-arrays transport layout. If that pattern is unfamiliar, the Wikipedia article on "AoS and SoA" is a good short reference for why packed columns are often friendlier to hot-path data movement than arrays of rich objects.
Parameters:
playbackState- Current mutable playback state.
Returns: Immutable frame snapshot for the host.
createWorkerPlaybackSnapshotBuffers
createWorkerPlaybackSnapshotBuffers(
pipeCount: number,
birdCount: number,
useSharedBuffers: boolean,
): WorkerPlaybackSnapshotBuffers
Creates typed-array storage for one packed snapshot.
Parameters:
pipeCount- Number of visible pipes to pack.birdCount- Number of playback birds to pack.useSharedBuffers- Whether buffers should be reusable shared memory.
Returns: Snapshot buffer shelf.
isTransferableArrayBuffer
isTransferableArrayBuffer(
buffer: ArrayBufferLike,
): boolean
Narrows transfer-list candidates to transferable ArrayBuffers.
Parameters:
buffer- Typed-array backing buffer.
Returns: True when the buffer can be passed through postMessage transfer list.
resolveWorkerPlaybackSnapshotBuffers
resolveWorkerPlaybackSnapshotBuffers(
playbackState: WorkerPlaybackState,
pipeCount: number,
birdCount: number,
): WorkerPlaybackSnapshotBuffers
Resolves snapshot column storage for one playback state.
Parameters:
playbackState- Current mutable playback state.pipeCount- Number of visible pipes to pack.birdCount- Number of playback birds to pack.
Returns: Snapshot buffers sized for the current frame.
resolveWorkerPlaybackSnapshotTransferList
resolveWorkerPlaybackSnapshotTransferList(
snapshot: WorkerPlaybackFrameSnapshot,
): Transferable[]
Resolves transferable buffers for one packed playback snapshot.
The returned buffers should be passed as the second argument to
postMessage(...) so ownership moves to the host thread instead of copying
the typed-array contents.
That ownership transfer is a large part of why the worker can stream full population snapshots without forcing the main thread to pay unnecessary copy costs every frame.
Parameters:
snapshot- Packed playback snapshot posted back to the browser host.
Returns: Transfer list used to move typed-array buffers without copying.
flappy-evolution-worker/flappy-evolution-worker.simulation.types.ts
WorkerPlaybackFrameContext
Shared mutable inputs for one worker playback frame simulation pass.
Educational note: The frame service computes several derived geometry values once per logical frame and threads them through the substep helpers in this context object. That keeps the top-level simulation flow declarative while avoiding repeated argument sprawl across helper calls.
flappy-evolution-worker/flappy-evolution-worker.simulation.utils.ts
closeWorkerPopulationRenderState
closeWorkerPopulationRenderState(
playbackState: WorkerPlaybackState | undefined,
): Promise<void>
Closes any persistent inference channels carried by a playback state.
Parameters:
playbackState- Playback state being retired.
Returns: Promise resolved after all bird channels are closed.
createWorkerPopulationRenderState
createWorkerPopulationRenderState(
networks: default[],
rng: RngLike,
initialVisibleWorldWidthPx: number,
initialVisibleWorldHeightPx: number,
channelWorkerUrl: string | undefined,
): WorkerPlaybackState
Creates initial playback state for a population of networks.
Educational note: This is the ownership boundary for worker playback initialization. The frame simulation service mutates the returned state on every step, but only this helper decides how a fresh population is placed into the world at time zero.
Parameters:
networks- Population to visualize.rng- Deterministic random source.initialVisibleWorldWidthPx- Initial viewport width from host.initialVisibleWorldHeightPx- Initial viewport height from host.
Returns: Fresh mutable playback state.
Example:
const playbackState = createWorkerPopulationRenderState(
currentPopulation,
rng,
1280,
720,
);
flappy-evolution-worker/flappy-evolution-worker.simulation.frame.service.ts
advanceBirdPhysics
advanceBirdPhysics(
frameContext: WorkerPlaybackFrameContext,
): void
Integrates bird velocity and vertical motion for one control substep.
Parameters:
frameContext- Shared frame context for this logical frame.
Returns: Nothing.
advancePipes
advancePipes(
frameContext: WorkerPlaybackFrameContext,
): void
Advances all visible pipes and culls those that have left the camera window.
Parameters:
frameContext- Shared frame context for this logical frame.
Returns: Nothing.
commitPassedPipeProgress
commitPassedPipeProgress(
bird: WorkerPopulationBird,
pipe: WorkerPopulationPipe,
): void
Commits one passed-pipe progress increment for a bird when eligible.
Parameters:
bird- Mutable bird state.pipe- Pipe candidate to mark as passed.
Returns: Nothing.
incrementLivingBirdFrameCounters
incrementLivingBirdFrameCounters(
renderState: WorkerPlaybackState,
): void
Increments survival counters for birds that remain active at frame start.
Parameters:
renderState- Mutable playback state.
Returns: Nothing.
resolveBirdCollisionAgainstPipe
resolveBirdCollisionAgainstPipe(
bird: WorkerPopulationBird,
pipe: WorkerPopulationPipe,
frameContext: WorkerPlaybackFrameContext,
): boolean
Resolves whether a bird collides with one pipe corridor during this substep.
Parameters:
bird- Mutable bird state.pipe- Pipe candidate to test.frameContext- Shared frame context for this logical frame.
Returns: true when the bird overlaps the pipe body instead of the gap.
resolveBirdControlActions
resolveBirdControlActions(
frameContext: WorkerPlaybackFrameContext,
): Promise<number>
Runs policy evaluation and commits the resulting observation memory updates.
Parameters:
frameContext- Shared frame context for this logical frame.
Returns: Number of activation calls performed in the substep.
resolveBirdOutOfBounds
resolveBirdOutOfBounds(
bird: WorkerPopulationBird,
visibleWorldHeightPx: number,
): boolean
Resolves whether a bird has exceeded the vertical play area.
Parameters:
bird- Mutable bird state.visibleWorldHeightPx- Current visible world height.
Returns: true when the bird is outside the vertical bounds.
resolveBirdTerminationAndProgress
resolveBirdTerminationAndProgress(
frameContext: WorkerPlaybackFrameContext,
): void
Resolves bird deaths and passed-pipe progress after motion is applied.
Parameters:
frameContext- Shared frame context for this logical frame.
Returns: Nothing.
resolveCameraLeftXPx
resolveCameraLeftXPx(
visibleWorldWidthPx: number,
): number
Resolves the current left-edge of the visible world in world-space pixels.
Parameters:
visibleWorldWidthPx- Current visible world width.
Returns: Left edge x-position in world coordinates.
runWorkerPopulationControlSubstep
runWorkerPopulationControlSubstep(
frameContext: WorkerPlaybackFrameContext,
): Promise<number>
Advances one control substep of the worker playback simulation.
Parameters:
frameContext- Shared frame context for this logical frame.
Returns: Number of activation calls performed in the substep.
spawnPipeIfNeeded
spawnPipeIfNeeded(
frameContext: WorkerPlaybackFrameContext,
): void
Spawns a new pipe when the substep budget crosses the spawn boundary.
Parameters:
frameContext- Shared frame context for this logical frame.
Returns: Nothing.
stepWorkerPopulationFrame
stepWorkerPopulationFrame(
renderState: WorkerPlaybackState,
rng: RngLike,
difficultyProfile: SharedDifficultyProfile,
): Promise<number>
Advances the whole population simulation by one logical frame.
Educational note: One logical frame is internally split into smaller control substeps so the worker can make flap decisions, apply gravity, move pipes, spawn new pipes, and resolve collisions with better numerical stability than a single large integration jump.
This function is the main simulation ownership boundary for the folder.
The companion simulation.utils file creates initial state; this service is
responsible for mutating that state over time.
Parameters:
renderState- Mutable simulation state.rng- Deterministic random source for spawn variation.difficultyProfile- Active dynamic difficulty profile.
Returns: Number of policy activation calls made in this frame.
flappy-evolution-worker/flappy-evolution-worker.warm-start.service.ts
applyTeacherWarmStart
applyTeacherWarmStart(
templateNetwork: default,
trainingSet: { input: number[]; output: number[]; }[],
architectureProfileId: ExampleArchitectureProfileId,
): void
Applies the teacher phase that best matches the selected architecture family.
Parameters:
templateNetwork- Template network cloned from the current population.trainingSet- Synthetic heuristic dataset.architectureProfileId- Selected shared Flappy profile id.
Returns: Nothing.
applyTemplateWeightsWithNoise
applyTemplateWeightsWithNoise(
genome: default,
template: default,
rng: FlappyRng,
noise: { weightStdDev: number; biasStdDev: number; },
): void
Copies template parameters into a genome and injects small Gaussian noise.
Educational note: The template network gives generation 0 a shared prior, while the noise terms restore diversity so the population is still worth evolving.
Parameters:
genome- Target genome to mutate in-place.template- Trained template source network.rng- Deterministic random source for noise sampling.noise- Standard deviations for weight and bias perturbations.
Returns: Nothing.
applyWarmStartGenerationZero
applyWarmStartGenerationZero(
neatController: default,
warmStartState: WorkerWarmStartState,
dependencies: WorkerWarmStartDependencies,
): void
Applies the generation-zero warm-start body when the runtime is still eligible.
Parameters:
neatController- Initialized NEAT runtime.warmStartState- Mutable warm-start lifecycle state.dependencies- Injectable warm-start seams.
Returns: Nothing.
buildHeuristicPretrainSet
buildHeuristicPretrainSet(
rng: FlappyRng,
sampleCount: number,
): { input: number[]; output: number[]; }[]
Builds synthetic supervised samples for generation-0 behavior cloning.
Educational note: These samples are not recorded gameplay traces. They are synthetic states generated from the same observation pipeline used during real playback so the teacher labels and the evolved policy inputs stay in the same feature space. The critical rule is that the teacher must only read the same compact current-frame controller shelf that the live network will later receive.
Parameters:
rng- Deterministic random source.sampleCount- Requested number of synthetic samples.
Returns: Supervised dataset of input/output pairs.
buildWarmStartRolloutSeedBatch
buildWarmStartRolloutSeedBatch(
rng: FlappyRng,
seedCount: number,
): number[]
Builds the deterministic shared rollout seed batch used during warm-start refinement.
Parameters:
rng- Deterministic random source.seedCount- Requested seed count.
Returns: Shared rollout seed batch.
composeWarmStartSeedBatchEvaluation
composeWarmStartSeedBatchEvaluation(
episodeResults: readonly FlappyEpisodeResult[],
): FlappySeedBatchEvaluation
Composes the completed warm-start rollouts into aggregate evidence.
Parameters:
episodeResults- Completed rollout results before the deadline fired.
Returns: Aggregate warm-start evaluation metrics.
createWarmStartDeadline
createWarmStartDeadline(
architectureProfileId: ExampleArchitectureProfileId,
resolveCurrentTimeMs: () => number,
): WorkerWarmStartDeadline
Creates the optional deadline used by recurrent warm-start refinement.
Parameters:
architectureProfileId- Selected shared Flappy profile id.resolveCurrentTimeMs- Clock source used for deadline checks.
Returns: Warm-start deadline contract.
evaluateWarmStartTemplateAcrossRollouts
evaluateWarmStartTemplateAcrossRollouts(
templateNetwork: default,
sharedRolloutSeeds: readonly number[],
warmStartDeadline: WorkerWarmStartDeadline,
runWarmStartRollout: WorkerWarmStartRolloutRunner,
): FlappySeedBatchEvaluation
Evaluates one warm-start template across the shared rollout seed batch.
Parameters:
templateNetwork- Candidate template to score.sharedRolloutSeeds- Shared rollout seeds used for stable comparison.warmStartDeadline- Deadline that can stop seed evaluation early.runWarmStartRollout- Rollout runner used to evaluate each seed.
Returns: Aggregate shared-seed evaluation.
interpolateValue
interpolateValue(
startValue: number,
endValue: number,
ratio: number,
): number
Linearly interpolates between two scalar values.
Parameters:
startValue- Value at ratio0.endValue- Value at ratio1.ratio- Interpolation ratio.
Returns: Interpolated value.
isWarmStartDeadlineExpired
isWarmStartDeadlineExpired(
warmStartDeadline: WorkerWarmStartDeadline,
): boolean
Resolves whether rollout refinement should yield to regular NEAT evolution.
Parameters:
warmStartDeadline- Deadline contract for the current warm-start pass.
Returns: True when the warm-start assist has spent its allowed budget.
isWarmStartEvaluationBetter
isWarmStartEvaluationBetter(
candidateEvaluation: FlappySeedBatchEvaluation,
bestEvaluation: FlappySeedBatchEvaluation,
architectureProfileId: ExampleArchitectureProfileId,
): boolean
Resolves whether the candidate batch evaluation beats the current best one.
Robust fitness is the primary signal. Mean pipe progress and mean frame survival act as deterministic tie-breakers so upgrades remain stable when the robust score is identical.
Parameters:
candidateEvaluation- Newly scored candidate aggregate.bestEvaluation- Current best aggregate.architectureProfileId- Selected shared Flappy profile id.
Returns: True when the candidate should replace the incumbent template.
optimizeWarmStartTemplateNetwork
optimizeWarmStartTemplateNetwork(
templateNetwork: default,
workerInitSeed: number,
architectureProfileId: ExampleArchitectureProfileId,
warmStartDeadline: WorkerWarmStartDeadline,
runWarmStartRollout: WorkerWarmStartRolloutRunner,
): default
Refines the generation-0 template against real Flappy rollouts.
The warm-start teacher gets the template out of pure-random territory, but it still only imitates a simple flap heuristic. This refinement pass keeps the topology fixed and searches the parameter surface directly against actual rollout fitness so the first visible generation starts closer to competent control.
Parameters:
templateNetwork- Heuristic-pretrained template network.workerInitSeed- Deterministic worker seed.architectureProfileId- Selected shared Flappy profile id.warmStartDeadline- Optional recurrent assist deadline.runWarmStartRollout- Rollout runner used to score candidate templates.
Returns: Best rollout-refined template found within the bounded budget.
perturbNetworkParametersInPlace
perturbNetworkParametersInPlace(
network: default,
rng: FlappyRng,
noise: { weightStdDev: number; biasStdDev: number; },
): void
Applies additive Gaussian noise to an existing network in-place.
Unlike the later population seeding copy step, this helper perturbs the candidate template directly so the rollout optimizer can evaluate one local parameter move at a time while keeping the topology unchanged.
Parameters:
network- Candidate template to perturb.rng- Deterministic random source.noise- Standard deviations for weight and bias perturbations.
Returns: Nothing.
resolveHeuristicTeacherFlapDecisionFromObservationVector
resolveHeuristicTeacherFlapDecisionFromObservationVector(
observationVector: readonly number[],
): boolean
Heuristic teacher policy used to label synthetic pretraining samples.
The rule intentionally stays simple and interpretable: flap when the bird is meaningfully below the next gap center, not already rising fast, and either near the next pipe or drifting close to the lower edge of the current gap.
The key constraint is architectural consistency: this teacher reads only the same compact 6-value controller vector used by regular training and playback. Extra legacy-derived features must not influence generation-zero labels.
Parameters:
observationVector- Compact current-frame controller input vector.
Returns: True when the teacher says to flap.
resolveWarmStartAnnealRatio
resolveWarmStartAnnealRatio(
optimizationStepIndex: number,
totalOptimizationSteps: number,
): number
Resolves the annealing ratio for rollout-guided warm-start refinement.
Parameters:
optimizationStepIndex- Zero-based optimization step index.totalOptimizationSteps- Total number of optimization steps.
Returns: Clamped ratio in the inclusive range [0, 1].
resolveWarmStartEvaluationScore
resolveWarmStartEvaluationScore(
aggregateEvaluation: FlappySeedBatchEvaluation,
architectureProfileId: ExampleArchitectureProfileId,
): number
Resolves the architecture-specific scalar used during warm-start rollout refinement.
NARX, GRU, and LSTM use a pipe-first scalar so rollout refinement prefers real pipe progress over a dense-shaping local optimum before the browser NEAT loop begins.
Parameters:
aggregateEvaluation- Shared-seed rollout evidence for one template.architectureProfileId- Selected shared Flappy profile id.
Returns: Scalar score used for candidate comparison.
resolveWarmStartRolloutOptimizationPlan
resolveWarmStartRolloutOptimizationPlan(
architectureProfileId: ExampleArchitectureProfileId,
): WorkerWarmStartRolloutOptimizationPlan
Resolves the rollout-refinement budget for one warm-start architecture profile.
NARX gets a stronger rollout pass, while GRU keeps a smaller bounded pass so the browser worker stays responsive after the Flappy-specific readout shortcut expands the recurrent seed.
Parameters:
architectureProfileId- Selected shared Flappy profile id.
Returns: Shared-seed count, optimization-step budget, and optional time cap.
resolveWorkerWarmStartTeacherStrategy
resolveWorkerWarmStartTeacherStrategy(
architectureProfileId: ExampleArchitectureProfileId,
): WorkerWarmStartTeacherStrategy
Resolves which teacher path should run before rollout refinement.
Parameters:
architectureProfileId- Selected shared Flappy profile id.
Returns: Teacher strategy best matched to the architecture family.
sampleGaussian
sampleGaussian(
rng: FlappyRng,
): number
Samples one standard-normal value using the Box-Muller transform.
If you are unfamiliar with the transform, the Wikipedia article on "Box-Muller transform" is a useful short background read. The worker uses it here because it is deterministic, dependency-light, and good enough for small noise injection during warm-start diversification.
Parameters:
rng- Deterministic random source.
Returns: One approximately standard-normal random value.
warmStartWorkerGenerationZeroIfNeeded
warmStartWorkerGenerationZeroIfNeeded(
neatController: default,
warmStartState: WorkerWarmStartState,
dependencies: WorkerWarmStartDependencies,
): Promise<void>
Applies a one-time generation-0 warm-start to improve initial demo quality.
Educational note: The worker entry should stay protocol-first. This service owns the short supervised bootstrap pass that nudges generation 0 away from pure noise while preserving the later NEAT-driven search loop.
Conceptually this is a lightweight behavior-cloning pass. If you want more background, the Wikipedia article on "imitation learning" is a helpful bridge between the heuristic teacher used here and the later evolutionary search.
Parameters:
neatController- Initialized NEAT runtime.warmStartState- Mutable warm-start lifecycle state.dependencies- Injectable warm-start seams for tests and runtime customization.
Returns: Promise resolved when warm-start evaluation finishes.
Example:
await warmStartWorkerGenerationZeroIfNeeded(neatRuntime, {
workerInitSeed: 123,
generationZeroWarmStartApplied: false,
});
WorkerWarmStartDeadline
Deadline contract used by recurrent rollout refinement.
WorkerWarmStartDependencies
Dependency bag for generation-0 warm-start orchestration.
The production path uses the real heuristic dataset builder and rollout-guided template refinement. Tests can override these seams to keep assertions small and deterministic.
WorkerWarmStartRolloutOptimizationPlan
Rollout-refinement budget resolved for one warm-start architecture profile.
WorkerWarmStartRolloutRunner
WorkerWarmStartRolloutRunner(
templateNetwork: default,
rolloutOptions: FlappyRolloutOptions,
): FlappyEpisodeResult
Callable shape used to evaluate one warm-start rollout candidate.
Tests can inject this seam to observe deadline hooks without running the full Flappy simulator, while production uses the real rollout service.
WorkerWarmStartState
State carried between generation requests for one worker runtime.
The warm-start service is intentionally one-shot. These fields let the worker remember whether generation 0 has already been bootstrapped and which initial RNG seed should be reused for deterministic synthetic sample generation.
flappy-evolution-worker/flappy-evolution-worker.errors.ts
createWorkerErrorMessage
createWorkerErrorMessage(
message: string,
): WorkerErrorMessage
Creates a typed worker error response payload from a message string.
Parameters:
message- Error message text.
Returns: Worker error response message.
Example:
postWorkerMessage(
createWorkerErrorMessage('Playback step requested before playback start.'),
);
createWorkerErrorMessageFromUnknown
createWorkerErrorMessageFromUnknown(
error: unknown,
): WorkerErrorMessage
Creates a typed worker error response payload from an unknown thrown value.
This helper keeps the protocol boundary narrow: worker internals can use
regular exceptions, while the browser host still receives one predictable
WorkerErrorMessage shape.
Parameters:
error- Unknown thrown value.
Returns: Worker error response message.
FLAPPY_WORKER_INIT_FAILED_ERROR_MESSAGE
Worker error emitted when runtime initialization fails unexpectedly.
This message is intentionally stable so the browser host can show a readable error without leaking internal exception shapes into the UI contract.
FLAPPY_WORKER_PLAYBACK_START_REQUIRES_GENERATION_ERROR_MESSAGE
Worker error emitted when playback start is requested before evolution output exists.
Playback is defined over an already-evolved population snapshot. The host must request at least one generation before asking the worker to start playback.
FLAPPY_WORKER_PLAYBACK_STEP_REQUIRES_START_ERROR_MESSAGE
Worker error emitted when playback stepping is requested before playback start.
The protocol is stateful: start-playback materializes the mutable playback
state that later request-playback-step messages advance.
resolveWorkerUnknownErrorMessage
resolveWorkerUnknownErrorMessage(
error: unknown,
): string
Resolves unknown error-like values into display-safe worker error messages.
Educational note:
Browser workers can throw anything, including strings or arbitrary objects.
Normalizing that value here gives the rest of the protocol a simple
string-only error surface.
Parameters:
error- Unknown error value thrown by worker logic.
Returns: Normalized error message string.
flappy-evolution-worker/flappy-evolution-worker.constants.ts
Synthetic sample count used for generation-0 warm-start pretraining.
Educational note: The warm-start service briefly trains a template network on a heuristic teacher before the first NEAT generation is evolved. This value controls how many synthetic state/action examples are generated for that bootstrap pass. Larger values usually make the teacher signal more stable, but they also increase startup latency inside the worker.
FLAPPY_WORKER_GEN0_PRETRAIN_BATCH_SIZE
Batch size for generation-0 warm-start pretraining.
Smaller batches inject a bit more stochasticity into the bootstrap fit, while still keeping the pass cheap enough for a browser worker.
FLAPPY_WORKER_GEN0_PRETRAIN_BIAS_NOISE_STDDEV
Gaussian standard deviation used for post-pretrain node-bias diversification.
Bias noise is slightly smaller than weight noise so the warm-start remains a prior, not a rigid clone of the teacher-fitted template.
FLAPPY_WORKER_GEN0_PRETRAIN_ITERATIONS
Optimizer iteration budget for generation-0 warm-start pretraining.
The goal is not to fully solve Flappy Bird with supervised learning. The worker only needs a short nudge away from completely random action logits so the first browser-visible generation looks less chaotic.
FLAPPY_WORKER_GEN0_PRETRAIN_RATE
Learning rate for generation-0 warm-start pretraining.
This is intentionally moderate: the template network should learn a simple corridor-following prior without overfitting the heuristic teacher.
FLAPPY_WORKER_GEN0_PRETRAIN_ROLLOUT_BIAS_STDDEV_END
Final node-bias noise scale for rollout-guided template refinement.
FLAPPY_WORKER_GEN0_PRETRAIN_ROLLOUT_BIAS_STDDEV_START
Initial node-bias noise scale for rollout-guided template refinement.
FLAPPY_WORKER_GEN0_PRETRAIN_ROLLOUT_OPTIMIZATION_STEPS
Hill-climb step budget used by rollout-guided generation-0 template refinement.
Each step perturbs the current best template, evaluates it on the shared rollout seed batch, and keeps the candidate only when it improves robust fitness. The budget stays intentionally small so worker startup remains fast.
FLAPPY_WORKER_GEN0_PRETRAIN_ROLLOUT_SEED_COUNT
Shared-seed batch size used by rollout-guided generation-0 template refinement.
After the heuristic teacher fit, the worker evaluates the fixed topology on a few real Flappy rollouts so the warm-start prior is pushed toward trajectories that actually survive the environment instead of only matching the synthetic teacher labels.
FLAPPY_WORKER_GEN0_PRETRAIN_ROLLOUT_WEIGHT_STDDEV_END
Final connection-weight noise scale for rollout-guided template refinement.
FLAPPY_WORKER_GEN0_PRETRAIN_ROLLOUT_WEIGHT_STDDEV_START
Initial connection-weight noise scale for rollout-guided template refinement.
Early optimization steps search broadly, then later steps cool toward the smaller end scale below for finer local refinement.
FLAPPY_WORKER_GEN0_PRETRAIN_SAMPLE_COUNT
Synthetic sample count used for generation-0 warm-start pretraining.
Educational note: The warm-start service briefly trains a template network on a heuristic teacher before the first NEAT generation is evolved. This value controls how many synthetic state/action examples are generated for that bootstrap pass. Larger values usually make the teacher signal more stable, but they also increase startup latency inside the worker.
FLAPPY_WORKER_GEN0_PRETRAIN_VISIBLE_WORLD_WIDTH_PX
Visible world width used when generating synthetic warm-start samples.
This should roughly match the browser playback framing so the generated observation vectors look like the states the policy will later see during real worker playback.
FLAPPY_WORKER_GEN0_PRETRAIN_WEIGHT_NOISE_STDDEV
Gaussian standard deviation used for post-pretrain connection-weight diversification.
After the template network is trained once, each genome receives a noisy copy of its weights. That keeps generation 0 visually coherent while preserving enough diversity for NEAT to search meaningfully.