evolutionEngine

Population-level runtime contracts for the ASCII Maze evolution engine.

This folder is where one maze, one fitness story, and one NEAT controller turn into a repeatable training program. The public EvolutionEngine facade uses these contracts to normalize options, coordinate host callbacks, keep deterministic state reproducible, and decide when a curriculum phase has produced a winner worth carrying forward.

The important distinction is scale. mazeMovement/ explains one agent run. fitness.ts explains how that run is scored. dashboardManager/ explains how progress is shown to a human. evolutionEngine/ explains how many runs across many generations become one population-level search loop with stop reasons, telemetry, warm starts, and phase outcomes that the next maze can reuse.

This file is the right chapter opening because it names the public nouns of that loop before the reader hits pooled scratch buffers or hot-path helpers. It answers four questions quickly: what a caller can configure, what a host may observe or interrupt, what result the engine returns, and which shared runtime contexts keep the hot path allocation-light.

A useful mental model is to treat the engine as a control tower rather than the aircraft itself. The engine does not move the agent through one maze cell at a time. It schedules phases, batches generations, preserves deterministic state, and hands structured outcomes back to browser or terminal hosts.

Read the chapter in three passes. Start here for the public contracts and the meaning of a run result. Continue to engineState.types.ts when you want the shared scratch and toggle state that keeps the loop cheap. Finish with evolutionLoop.ts, evolutionEngine.services.ts, and sampling.ts when you want the actual orchestration and telemetry mechanics.

flowchart LR
  classDef base fill:#08131f,stroke:#1ea7ff,color:#dff6ff,stroke-width:1px;
  classDef accent fill:#0f2233,stroke:#ffd166,color:#fff4cc,stroke-width:1.5px;

  Caller["Caller or host"]:::base --> Options["IRunMazeEvolutionOptions\nrun inputs"]:::base
  Options --> Engine["EvolutionEngine facade\npopulation-level control"]:::accent
  Engine --> Loop["generation loop\nevaluate mutate telemetry"]:::base
  Loop --> Result["MazeEvolutionRunResult\nbest network + exit reason"]:::base
  Result --> Phase["MazeEvolutionCurriculumPhaseOutcome\ncarry winner forward"]:::base
  Engine --> Host["EvolutionHostAdapter\npause and stop hooks"]:::base
flowchart TD
  classDef base fill:#08131f,stroke:#1ea7ff,color:#dff6ff,stroke-width:1px;
  classDef accent fill:#0f2233,stroke:#ffd166,color:#fff4cc,stroke-width:1.5px;

  EngineFolder["evolutionEngine/"]:::accent --> Contracts["evolutionEngine.types.ts\npublic run contracts"]:::base
  EngineFolder --> Scratch["engineState.types.ts\nshared scratch and toggles"]:::base
  EngineFolder --> Loop["evolutionLoop.ts\nmain generation orchestration"]:::base
  EngineFolder --> Services["evolutionEngine.services.ts\nand helpers"]:::base
  EngineFolder --> Sampling["sampling.ts\nand telemetry support"]:::base

For background reading on the staged difficulty idea behind the browser and curriculum-style runs, see Wikipedia contributors, Curriculum learning, which captures the broader teaching idea of solving easier tasks before harder ones.

Example: describe the host adapter and reporting hooks the engine may call.

const hostAdapter: EvolutionHostAdapter = {
  isPauseRequested: () => window.asciiMazePaused === true,
  handleStop: ({ reason, completedGenerations }) => {
    console.log(reason, completedGenerations);
  },
};

Example: sketch one engine run configuration before execution begins.

const runOptions: IRunMazeEvolutionOptions = {
  mazeConfig: { maze },
  agentSimConfig: { maxSteps: 160 },
  evolutionAlgorithmConfig: { popSize: 120, deterministic: true },
  reportingConfig: { dashboardManager, logEvery: 5 },
};

evolutionEngine/evolutionEngine.types.ts

DistanceMap

Distance map for maze navigation.

EncodedMaze

Encoded maze representation with cell values.

EncodedMazeData

Encoded maze for simulation.

EvolutionEngineFacadeRuntimeState

Mutable runtime state owned by the public EvolutionEngine facade.

The extracted engine modules already share pooled buffers through engineState. This narrower state exists only for the facade-specific logits-ring bookkeeping that must survive across runs while keeping the class boundary orchestration-first.

EvolutionGenomeLike

Loose genome shape shared by engine telemetry and population-dynamics helpers.

EvolutionHelpers

Helper functions object passed to evolution loop orchestration.

EvolutionHostAdapter

Narrow host adapter used by the engine for pause polling and stop notifications.

Example:

const hostAdapter: EvolutionHostAdapter = {
  isPauseRequested: () => window.asciiMazePaused === true,
  handleStop: ({ reason }) => console.log('maze run stopped because', reason),
};

EvolutionHostStopEvent

Host-facing stop event emitted by the engine when a run finishes for a concrete reason.

EvolutionLoopHelpers

Helper functions for evolution.

EvolutionLoopRuntimeContext

Shared runtime buffers and limits consumed by the evolution loop hot path.

This context keeps the loop and simulation helpers from passing a long list of pooled ring buffers, scratch arrays, and capacity limits positionally.

EvolutionLoopSupportContext

Shared scratch buffers and helper callbacks used across evolution-loop stages.

This context groups the scratch arrays and analysis helpers that travel together through generation, simulation, and snapshot paths.

EvolutionLoopTelemetryContext

Shared telemetry thresholds consumed by the evolution loop simulation pass.

The loop owns these switches conceptually, but grouping them as one context keeps telemetry policy changes from widening hot-path function signatures.

EvolutionOptions

Options object passed to evolution functions.

EvolutionStopReason

Canonical stop reasons reported by the engine to host adapters.

FileSystem

Node.js fs module type for file operations.

IAgentSimulationConfig

Agent simulation configuration.

IEvolutionAlgorithmConfig

Configuration options for the evolutionary algorithm used in the ASCII Maze demos.

IMazeConfig

Maze configuration used by the ASCII Maze evolution helpers.

IReportingConfig

Reporting configuration used to control logging, dashboard updates and UI pacing.

IRunMazeEvolutionOptions

Main options for running a single maze-evolution experiment.

LogitsRingState

Ring state for logits tracking.

LoopHelpers

Loop helpers returned by prepareLoopHelpers.

MazeDistanceMap

Distance map for pathfinding.

MazeEvolutionCurriculumPhaseOutcome

Shared curriculum-facing summary derived from one completed evolution phase.

MazeEvolutionRunResult

Stable result returned by EvolutionEngine.runMazeEvolution().

MazePosition

Position in maze.

MutationOperationLike

Mutation-operation surface read from the NEAT driver at runtime.

The engine treats mutation operations as opaque descriptors because the concrete driver owns how those operations are interpreted. The helpers only need enough structure to cache, count, and hand them back into mutate(...).

NeatInstance

Type for Neat class instance from the neataptic library.

NetworkConnection

Network connection representation used by engine-side runtime adaptation helpers.

NetworkInstance

Type for Network class instance from the neataptic library.

NetworkNode

Network node representation used by engine-side runtime adaptation helpers.

PathModule

Node.js path module type.

Position

2D position in maze coordinates.

ProfilingAccumulators

Profiling accumulator structure.

ScratchBundle

Scratch bundle containing reusable buffers.

SimulationResult

Simulation result returned by generation evaluation helpers.

SnapshotEntry

Snapshot entry for persistence.

SpeciesHistoryHost

Static host used to read optional species-history state from the engine facade.

TelemetryNeatLike

NEAT runtime shape needed by telemetry helpers that inspect the population.

The telemetry helpers intentionally ask for very little here: access to the population plus optional telemetry export. That keeps them portable across the engine's internal helpers without binding them to the full driver surface.

TrackedNetworkInstance

Network instance annotated with telemetry fields during a generation.

TrainingConstants

Training constants used by Lamarckian warm-start and refinement helpers.

evolutionEngine/engineState.types.ts

Shared contracts for the ASCII Maze evolution engine subsystem.

The evolutionEngine/ folder is where the public EvolutionEngine facade fans out into the lower-level machinery that keeps long maze runs practical: pooled scratch state, deterministic RNG caches, telemetry workspaces, sampling helpers, warm-start support, and population-level recovery logic.

This file is the common footing for that subsystem. It defines the shared state and scratch-buffer contracts that let the rest of the engine stay allocation-light and orchestration-first instead of passing dozens of loose arrays and counters through every hot-path helper.

EngineProfilingState

Aggregated profiling configuration and accumulators shared across the evolution run.

EngineScratchState

Centralised shared state for the ASCII maze evolution façade.

Responsibilities:

  1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
  2. Expose runtime toggle state (EngineToggleState) that drives optional phases and telemetry density.
  3. Provide factory and maintenance helpers (createEngineState, initialiseTelemetryScratch, ensureVisitedHashCapacity, ensureRngCacheBatch, reseedRngState) that size buffers and keep deterministic RNG state in sync.
  4. Export the project-wide singleton engineState so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.

Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.

EngineState

Shared engine state instance combining pooled scratch buffers with toggle flags.

EngineToggleState

Runtime switches that adjust telemetry verbosity and optional training phases.

RngCacheHandles

Handles returned after ensuring the RNG cache is ready for consumption.

RngCacheParameters

Parameters controlling the RNG cache refill process.

TelemetryScratchHandles

Collection of scratch buffers handed back after initialisation for convenience.

TelemetryScratchRequest

Configuration describing which telemetry scratch buffers require capacity guarantees.

VisitedHashScratchHandles

Handles exposed after ensuring the visited-coordinate hash table capacity.

evolutionEngine/evolutionLoop.ts

Evolution Loop Module

Purpose:

Provides utilities for running the main NEAT evolution loop, including generation orchestration, cancellation checking, and loop helper preparation.

This module encapsulates:

ES2023 Policy:

checkCancellation

checkCancellation(
  options: EvolutionOptions,
  bestResult: IMazeRunResult | undefined,
): string | undefined

Inspect cooperative cancellation sources and annotate the provided result when cancelled.

This function checks for cancellation from two sources in priority order:

  1. Legacy cancellation object with isCancelled() method
  2. Standard AbortSignal with aborted property

Design Rationale:

Cancellation Priority:

  1. Check options.cancellation.isCancelled() (legacy API)
  2. Check options.signal.aborted (standard AbortSignal)
  3. Return undefined if no cancellation detected

Parameters:

Parameters:

Returns: A reason string ('cancelled' | 'aborted') when cancellation is detected, otherwise undefined

Examples:

// Check cancellation before starting next generation const cancelReason = checkCancellation(opts, runResult); if (cancelReason) return cancelReason;

// Check cancellation without result annotation if (checkCancellation(opts)) { console.log('User requested cancellation'); break; }

checkStopConditions

checkStopConditions(
  bestResult: IMazeRunResult | undefined,
  bestNetwork: default | null,
  maze: string[],
  completedGenerations: number,
  neat: default,
  dashboardManager: IDashboardManager | undefined,
  flushToFrame: () => Promise<void>,
  hostAdapter: EvolutionHostAdapter | undefined,
  minProgressToPass: number,
  autoPauseOnSolve: boolean,
  stopOnlyOnSolve: boolean,
  stagnantGenerations: number,
  maxStagnantGenerations: number,
  maxGenerations: number,
): Promise<string | undefined>

Inspect common termination conditions and perform minimal, best-effort side-effects.

This function checks three canonical stop reasons in priority order:

  1. Solved: Best result achieves minimum progress threshold
  2. Stagnation: No improvement for maxStagnantGenerations
  3. MaxGenerations: Absolute generation cap reached

Design Rationale:

Side Effects (Best-Effort):

Parameters:

Parameters:

Returns: A canonical reason string ('solved'|'stagnation'|'maxGenerations') when stopping, otherwise undefined

Example:

// Check stop conditions after each generation const reason = await checkStopConditions( bestResult, bestNet, maze, gen, neat, dashboard, flush, 95, true, false, stagnant, 500, 10000 ); if (reason) { console.log('Stopping due to', reason); break; }

emitProfileSummary

emitProfileSummary(
  engineState: EngineState,
  safeWrite: (msg: string) => void,
  completedGenerations: number,
  totalEvolveMs: number,
  totalLamarckMs: number,
  totalSimMs: number,
  isProfilingDetailsEnabledFn: (state: EngineState) => boolean,
  getProfilingAccumulatorsFn: (state: EngineState) => ProfilingAccumulators,
): void

Emit a formatted profiling summary showing average per-generation timings.

This function prints a compact profiling summary with average millisecond timings for the main evolution phases (evolve, Lamarckian training, simulation). If detailed profiling is enabled, it also prints averages for telemetry, simplify, snapshot, and prune operations.

Design Rationale:

Calculation Steps:

  1. Validate and normalize generation count (guard divide-by-zero)
  2. Store totals in pooled scratch buffer (4-slot Float64Array)
  3. Compute per-generation averages by dividing totals by generation count
  4. Format numbers with 2 decimal places and print compact summary
  5. If detailed profiling enabled, print averaged detail line

Parameters:

Parameters:

Example:

// Print averages after a run that completed 100 generations emitProfileSummary( state, console.log, 100, 12000, 3000, 4500, isProfilingDetailsEnabled, getProfilingAccumulators );

EvolutionLoopResult

Evolution loop result

GenerationOutcome

Generation outcome with profiling timings

MutableMazeResult

Mutable result object with exitReason field

persistSnapshotIfNeeded

persistSnapshotIfNeeded(
  engineState: EngineState,
  fs: { writeFileSync?: ((path: string, data: string) => void) | undefined; } | null,
  pathModule: { join?: ((...paths: string[]) => string) | undefined; } | null,
  persistDir: string | undefined,
  persistTopK: number,
  completedGenerations: number,
  persistEvery: number,
  neat: default,
  bestFitness: number,
  simplifyMode: boolean,
  plateauCounter: number,
  scratchSnapshotObj: Record<string, unknown>,
  scratchSnapshotTop: SnapshotEntry[],
  collectTelemetryTailFn: (state: EngineState, neat: default, count: number) => unknown,
  getSortedIndicesByScoreFn: (state: EngineState, population: default[]) => number[],
  isProfilingDetailsEnabledFn: (state: EngineState) => boolean,
  profilingStartTimestampFn: (state: EngineState) => number,
  accumulateProfilingDurationFn: (state: EngineState, label: string, duration: number) => void,
): void

Persist a population snapshot to disk at the configured interval.

This function writes a JSON snapshot of the current generation state when the generation cadence aligns with the configured persistence interval. It captures top-K genomes, telemetry tail, and metadata for later analysis or resume.

Design Rationale:

Scheduling Logic:

Snapshot Structure:

Parameters:

Parameters:

Example:

// Persist snapshot every 25 generations persistSnapshotIfNeeded( state, fs, path, './snapshots', 10, 50, 25, neat, 0.95, false, 3, scratchObj, scratchTop, collectTail, getSorted, isProfilingEnabled, profileStart, profileAccum );

prepareLoopHelpers

prepareLoopHelpers(
  opts: EvolutionOptions,
  scratchBundle: ScratchBundle,
): LoopHelpers

Build lightweight helpers used inside the evolution loop.

This function assembles the helper utilities needed by the main evolution loop:

Design Rationale:

Scratch Buffer Warm-Up:

Parameters:

Parameters:

Returns: Object containing:

Example:

// Prepare loop helpers with scratch buffer warm-up const { flushToFrame, fs, path, safeWrite } = prepareLoopHelpers(opts, engineState.scratch); safeWrite('Starting evolution...\n'); await flushToFrame(); // Yield to host

runEvolutionLoop

runEvolutionLoop(
  engineState: EngineState,
  neat: default,
  opts: EvolutionOptions,
  lamarckianTrainingSet: { input: number[]; output: number[]; }[],
  encodedMaze: number[][],
  startPosition: readonly [number, number],
  exitPosition: readonly [number, number],
  distanceMap: number[][],
  helpers: LoopHelpers,
  doProfile: boolean,
  runtimeContext: EvolutionLoopRuntimeContext,
  initialRingState: LogitsRingState,
  telemetryContext: EvolutionLoopTelemetryContext,
  supportContext: EvolutionLoopSupportContext,
  constants: TrainingConstants & { DEFAULT_TRAIN_BATCH_LARGE: number; FITTEST_TRAIN_ITERATIONS: number; TELEMETRY_MINIMAL: boolean; SATURATION_PRUNE_THRESHOLD: number; RECENT_WINDOW: number; REDUCED_TELEMETRY: boolean; DISABLE_BALDWIN: boolean; },
): Promise<EvolutionLoopResult>

Internal evolution loop that executes generations until a stop condition or cancellation.

Behaviour & contract:

Parameters:

Returns: Promise resolving to an object: { bestNetwork, bestResult, neat, completedGenerations, totalEvolveMs, totalLamarckMs, totalSimMs, updatedRingState }

Example:

const runSummary = await runEvolutionLoop( state, neat, opts, trainingSet, maze, start, exit, distMap, helpers, true, ... );

runGeneration

runGeneration(
  engineState: EngineState,
  neat: default,
  doProfile: boolean,
  lamarckianIterations: number,
  lamarckianTrainingSet: { input: number[]; output: number[]; }[],
  lamarckianSampleSize: number | undefined,
  safeWrite: (msg: string) => void,
  completedGenerations: number,
  dynamicPopEnabled: boolean,
  dynamicPopMax: number,
  plateauGenerations: number,
  plateauCounter: number,
  dynamicPopExpandInterval: number,
  dynamicPopExpandFactor: number,
  dynamicPopPlateauSlack: number,
  speciesHistoryRef: number[],
  emptyVec: default[],
  scratchNodeIdx: Int32Array<ArrayBufferLike>,
  getNodeIndicesByType: (nodes: NetworkNode[], type: string) => number,
  constants: TrainingConstants,
): Promise<GenerationOutcome>

Run one generation: evolve, ensure output identity, update species history, maybe expand population, and run Lamarckian training if configured.

Behaviour & contract:

Parameters:

Returns: An object shaped { fittest, tEvolve, tLamarck } where:

Example:

// Run a single generation with profiling and optional Lamarckian warm-start const { fittest, tEvolve, tLamarck } = await runGeneration( engineState, neatInstance, true, // doProfile 5, // lamarckianIterations trainingSet, 16, // lamarckianSampleSize console.log, genIndex, true, 500, 10, plateauCounter, 5, 0.1, 0.75, speciesHistory, [], nodeIndexBuffer, getNodeIndicesByTypeFn, { DEFAULT_TRAIN_ERROR: 0.01, ... } );

SimResultWithOutputs

Simulation result with step outputs

simulateAndPostprocess

simulateAndPostprocess(
  engineState: EngineState,
  fittest: default,
  encodedMaze: number[][],
  startPosition: readonly [number, number],
  exitPosition: readonly [number, number],
  distanceMap: number[][],
  maxSteps: number | undefined,
  doProfile: boolean,
  safeWrite: (msg: string) => void,
  logEvery: number,
  completedGenerations: number,
  neat: default,
  runtimeContext: EvolutionLoopRuntimeContext,
  ringState: LogitsRingState,
  telemetryContext: EvolutionLoopTelemetryContext,
  loopSupportContext: Pick<EvolutionLoopSupportContext, "loopHelpers">,
): SimulationResult

Simulate the supplied fittest genome/network and perform allocation-light postprocessing.

Behaviour & contract:

Steps (high level):

  1. Run the simulator and capture wall-time when doProfile is truthy.
  2. Attach compact telemetry fields to fittest and ensure legacy _lastStepOutputs exists.
  3. If per-step logits are available, ensure ring capacity and copy them into the selected ring.
  4. Optionally prune saturated hidden->output connections and emit telemetry via logGenerationTelemetry.
  5. Return the raw simulation result and elapsed simulation time (ms when profiling enabled).

Notes on pooling / reentrancy:

Parameters:

Returns: An object { generationResult, simTime, updatedRingState } where simTime is ms when profiling is enabled

Example:

const { generationResult, simTime, updatedRingState } = simulateAndPostprocess( state, bestGenome, maze, start, exit, distMap, 1000, true, console.log, 10, genIdx, neat, ... );

SimulationOutcome

Simulation result with profiling and ring state

updateDashboardAndMaybeFlush

updateDashboardAndMaybeFlush(
  maze: string[],
  result: IMazeRunResult | undefined,
  network: default | null,
  completedGenerations: number,
  neat: default,
  dashboardManager: IDashboardManager | undefined,
  flushToFrame: (() => Promise<void>) | undefined,
): Promise<void>

Safely update a UI dashboard with the latest run state and optionally yield to the host/frame via an awaited flush function.

Behaviour (best-effort):

  1. If dashboardManager.update exists and is callable, call it with the stable argument order (maze, result, network, completedGenerations, neat). Any exception raised by the dashboard is swallowed to avoid interrupting the evolution loop.
  2. If flushToFrame is supplied as an async function, await it to yield control to the event loop or renderer (for example () => new Promise(r => requestAnimationFrame(r))).
  3. The helper avoids heap allocations and relies on existing pooled scratch buffers in the engine for heavy telemetry elsewhere; this method intentionally performs only short-lived control flow and minimal work.

Design Rationale:

Parameters:

Parameters:

Example:

// Yield to the browser's next repaint after dashboard update: await updateDashboardAndMaybeFlush( maze, genResult, fittestNetwork, gen, neatInstance, dashboard, () => new Promise(r => requestAnimationFrame(r)) );

updateDashboardPeriodic

updateDashboardPeriodic(
  maze: string[],
  bestResult: IMazeRunResult | undefined,
  bestNetwork: default | null,
  completedGenerations: number,
  neat: default,
  dashboardManager: IDashboardManager | undefined,
  flushToFrame: (() => Promise<void>) | undefined,
): Promise<void>

Periodic dashboard update used when the engine wants to refresh a non-primary dashboard view (for example background or periodic reporting). This helper is intentionally small, allocation-light and best-effort: dashboard errors are swallowed so the evolution loop cannot be interrupted by UI issues.

Behavioural contract:

  1. If dashboardManager.update is present and callable the method invokes it with the stable argument order: (maze, bestResult, bestNetwork, completedGenerations, neat).
  2. If flushToFrame is supplied the helper awaits it after the update to yield to the host renderer (eg. requestAnimationFrame). Any exceptions raised by the flush are swallowed.
  3. The helper avoids creating ephemeral arrays/objects and therefore does not use typed-array scratch buffers here — there is no hot numerical work to pool. Other engine helpers already reuse class-level scratch buffers where appropriate.

Design Rationale:

Steps / inline intent:

  1. Fast-guard when an update cannot be performed (missing manager, update method, or missing content to visualise).
  2. Call the dashboard update in a try/catch to preserve best-effort semantics.
  3. Optionally await the provided flushToFrame function to yield to the host.

Parameters:

Parameters:

Example:

// Safe periodic update and yield to next frame await updateDashboardPeriodic( maze, result, network, gen, neatInstance, dashboard, () => new Promise(r => requestAnimationFrame(r)) );

evolutionEngine/evolutionEngine.services.ts

applyEvolutionEngineRingState

applyEvolutionEngineRingState(
  updatedRingState: LogitsRingState,
): void

Apply the latest logits-ring runtime values returned by the evolution loop.

Parameters:

configureEvolutionEngineToggles

configureEvolutionEngineToggles(
  reducedTelemetry: boolean,
  telemetryMinimal: boolean,
  disableBaldwinPhase: boolean,
): void

Apply telemetry and Baldwin-phase toggles derived from one normalized run request.

Parameters:

getEvolutionEngineFacadeRuntimeState

getEvolutionEngineFacadeRuntimeState(): LogitsRingState

Read the mutable facade-owned logits-ring runtime state.

Returns: Current ring-capacity, shared-mode, and write-cursor state.

getEvolutionEngineMaxLogitsRingCapacity

getEvolutionEngineMaxLogitsRingCapacity(): number

Read the hard maximum ring capacity used by the public facade.

Returns: Maximum ring capacity allowed for logits telemetry.

getEvolutionEngineSharedState

getEvolutionEngineSharedState(): EngineState

Return the shared engine singleton used by extracted engine modules.

The public facade now depends on the same owner as the rest of the engine boundary instead of creating a private duplicate singleton.

Returns: Shared engine state singleton.

resetEvolutionEngineRingState

resetEvolutionEngineRingState(): void

Reset the facade-owned logits-ring runtime state to its baseline defaults.

evolutionEngine/sampling.ts

Sampling and history helpers extracted from the ASCII maze evolution façade.

Responsibilities:

  1. Provide allocation-light array sampling utilities that reuse the shared EngineState scratch pools.
  2. Expose history helpers that mirror the façade behaviour while keeping pooled buffers centralised.
  3. Centralise RNG parameter resolution for sampling paths to keep behaviour deterministic under shared state.

These helpers look small, but they sit under several hot paths. The main job is not "random choice" in the abstract; it is random choice without quietly reintroducing per-generation array churn into long-running experiments.

getTail

getTail(
  state: EngineState,
  source: T[] | undefined,
  count: number,
): T[]

Extract the last count items from source into the shared tail buffer.

Steps:

  1. Validate the source array and clamp the requested tail length.
  2. Grow the pooled tail buffer to the next power of two when necessary.
  3. Copy the suffix into the pooled buffer and trim its logical length.

Parameters:

Returns: Pooled array containing the requested tail slice.

Example:

const recent = getTail(sharedState, telemetryLog, 40);

pushHistory

pushHistory(
  buffer: T[] | undefined,
  value: T,
  maxLength: number,
): T[]

Proxy to {@link MazeUtils.pushHistory} for consistency with the façade API.

Parameters:

Returns: Updated history buffer with value appended and trimmed to maxLength.

Example:

const history = pushHistory(existingHistory, snapshot, 20);

sampleArray

sampleArray(
  state: EngineState,
  source: T[],
  sampleCount: number,
): T[]

Sample sampleCount items (with replacement) from source into the pooled scratch buffer.

This helper is for callers that want an ephemeral array view immediately. The returned array reuses shared scratch storage, so callers should copy it first if they need the contents to survive another helper call.

Steps:

  1. Validate the input array and normalise sampleCount to an integer.
  2. Grow the shared sampleResultBuffer to the next power of two when capacity is insufficient.
  3. Fill the pooled buffer using the shared fast RNG and truncate the logical length to sampleCount.

Parameters:

Returns: Pooled ephemeral array containing the sampled elements.

Example:

const picks = sampleArray(sharedState, population, 16); const safeCopy = [...picks];

sampleIntoScratch

sampleIntoScratch(
  state: EngineState,
  source: T[],
  sampleCount: number,
): number

Sample up to sampleCount items (with replacement) into the shared samplePool buffer.

Compared with sampleArray, this form is for callers that only need a count plus access to the pooled buffer on state.scratch.samplePool. That avoids one more logical array object on the hottest paths.

Steps:

  1. Validate inputs and ensure the pooled buffer exists.
  2. Grow the buffer by powers of two when more capacity is required.
  3. Fill the buffer with random selections and return the number of items written.

Parameters:

Returns: Number of sampled elements written into the pooled buffer.

Example:

const sampled = sampleIntoScratch(sharedState, population, 24); for (let index = 0; index < sampled; index++) { processGenome(sharedState.scratch.samplePool[index]); }

sampleSegmentIntoScratch

sampleSegmentIntoScratch(
  state: EngineState,
  source: T[],
  segmentStart: number,
  sampleCount: number,
): number

Sample from a suffix of source starting at segmentStart into the pooled buffer.

The common use case is "sample from the non-elite tail" or some later slice of a population without first allocating source.slice(segmentStart).

Steps:

  1. Clamp indices and ensure there is a non-empty segment.
  2. Ensure the pooled buffer can store the requested sample count.
  3. Fill the buffer via the shared RNG, returning the number of items written.

Parameters:

Returns: Number of elements written into the pooled buffer.

Example:

const written = sampleSegmentIntoScratch(sharedState, population, elitismCount, 12); const genome = sharedState.scratch.samplePool[0];

evolutionEngine/engineState.ts

Centralised shared state for the ASCII maze evolution façade.

Responsibilities:

  1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
  2. Expose runtime toggle state (EngineToggleState) that drives optional phases and telemetry density.
  3. Provide factory and maintenance helpers (createEngineState, initialiseTelemetryScratch, ensureVisitedHashCapacity, ensureRngCacheBatch, reseedRngState) that size buffers and keep deterministic RNG state in sync.
  4. Export the project-wide singleton engineState so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.

Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.

createEngineState

createEngineState(): EngineState

Fabricates a new {@link EngineState} with pre-sized scratch buffers and default toggle values.

Returns: Initialized engine state used by the maze evolution façade.

createProfilingState

createProfilingState(): EngineProfilingState

Fabricates the profiling state bundle consumed by timing helpers.

Returns: Profiling configuration and accumulator state.

Example:

const profiling = createProfilingState(); console.log(profiling.detailsEnabled); // false unless env flag set

createScratchState

createScratchState(): EngineScratchState

Fabricates the default pooled scratch buffers shared by the evolution façade.

Returns: Scratch bundle sized for the baseline maze curriculum workload.

Example:

const scratch = createScratchState(); console.log(scratch.exps.length); // 4

createToggleState

createToggleState(): EngineToggleState

Fabricates the default runtime toggle bundle for the evolution façade.

Returns: Toggle state with all optional phases enabled.

Example:

const toggles = createToggleState(); console.log(toggles.reducedTelemetry); // false

DEFAULT_RNG_CACHE_BATCH_SIZE

Default RNG cache batch size mirroring the façade constant.

DEFAULT_VISITED_HASH_LOAD_FACTOR

Default load factor target for the visited coordinate hash table.

EngineProfilingState

Aggregated profiling configuration and accumulators shared across the evolution run.

EngineScratchState

Centralised shared state for the ASCII maze evolution façade.

Responsibilities:

  1. Define the scratch-buffer schema consumed by telemetry, population, and inspection helpers.
  2. Expose runtime toggle state (EngineToggleState) that drives optional phases and telemetry density.
  3. Provide factory and maintenance helpers (createEngineState, initialiseTelemetryScratch, ensureVisitedHashCapacity, ensureRngCacheBatch, reseedRngState) that size buffers and keep deterministic RNG state in sync.
  4. Export the project-wide singleton engineState so extracted modules can share the façade’s pooled resources while still accepting injected state for testing.

Callers mutate the returned scratch instances in place to avoid per-generation allocations; higher-level modules should treat the helpers as the sole entry point for sizing or resetting shared buffers.

EngineState

Shared engine state instance combining pooled scratch buffers with toggle flags.

EngineToggleState

Runtime switches that adjust telemetry verbosity and optional training phases.

ensureRngCacheBatch

ensureRngCacheBatch(
  parameters: RngCacheParameters,
  state: EngineState,
): RngCacheHandles

Ensure the RNG cache contains fresh samples before consumption.

Parameters:

Returns: Handles exposing the cache and its batch size.

ensureVisitedHashCapacity

ensureVisitedHashCapacity(
  targetEntryCount: number,
  state: EngineState,
): VisitedHashScratchHandles

Ensure the visited-coordinate hash table can store the requested entry count.

Growth policy:

  1. Tables expand geometrically (powers of two) while respecting the configured load factor.
  2. When the existing table is large enough, it is cleared in place to preserve the allocation.
  3. Load factors outside the (0,1) range fall back to {@link DEFAULT_VISITED_HASH_LOAD_FACTOR}.

Parameters:

Returns: Cleared table reference plus the slot mask for probing loops.

Example:

const { table, slotMask } = ensureVisitedHashCapacity(256);

initialiseTelemetryScratch

initialiseTelemetryScratch(
  request: TelemetryScratchRequest,
  state: EngineState,
): TelemetryScratchHandles

Ensure telemetry-related scratch buffers allocate enough capacity for upcoming work.

Growth strategy:

Parameters:

Returns: Handles referencing the ensured scratch buffers for immediate use.

reseedRngState

reseedRngState(
  seed: number,
  state: EngineState,
): number

Persist a new RNG seed and schedule a cache refill on the next draw.

Parameters:

Returns: Unsigned seed stored in scratch state for diagnostics.

RngCacheHandles

Handles returned after ensuring the RNG cache is ready for consumption.

RngCacheParameters

Parameters controlling the RNG cache refill process.

setBaldwinPhaseDisabledFlag

setBaldwinPhaseDisabledFlag(
  isDisabled: boolean,
): void

Enable or disable the Baldwin-phase warm-start pipeline.

Parameters:

setReducedTelemetryFlag

setReducedTelemetryFlag(
  isEnabled: boolean,
): void

Toggle the reduced telemetry mode.

Parameters:

setTelemetryMinimalFlag

setTelemetryMinimalFlag(
  isMinimal: boolean,
): void

Toggle the minimal telemetry mode for JSON output.

Parameters:

TelemetryScratchHandles

Collection of scratch buffers handed back after initialisation for convenience.

TelemetryScratchRequest

Configuration describing which telemetry scratch buffers require capacity guarantees.

VisitedHashScratchHandles

Handles exposed after ensuring the visited-coordinate hash table capacity.

evolutionEngine/rngAndTiming.ts

RNG and timing helpers extracted from the evolution façade.

accumulateProfilingDuration

accumulateProfilingDuration(
  state: EngineState,
  category: string,
  deltaMs: number,
): void

Accumulate a profiling duration under the supplied key when detail profiling is enabled.

Parameters:

Returns: void.

clearDeterministicMode

clearDeterministicMode(
  state: EngineState,
): void

Disable deterministic RNG mode.

Parameters:

Returns: void.

drawFastRandom

drawFastRandom(
  state: EngineState,
  parameters: RngCacheParameters,
): number

Generate a fast uniform random sample using the shared RNG cache.

Parameters:

Returns: Uniform sample in the range [0, 1).

Example:

const sample = drawFastRandom(sharedState, rngParameters);

getProfilingAccumulators

getProfilingAccumulators(
  state: EngineState,
): Record<string, number>

Provide direct access to the profiling accumulator map.

Parameters:

Returns: Mutable record of profiling accumulators keyed by category name.

isDeterministicModeEnabled

isDeterministicModeEnabled(
  state: EngineState,
): boolean

Retrieve the deterministic RNG flag stored on the shared engine state.

Parameters:

Returns: True when deterministic mode is active.

isProfilingDetailsEnabled

isProfilingDetailsEnabled(
  state: EngineState,
): boolean

Determine whether detailed profiling accumulation is active.

Parameters:

Returns: True when detail profiling is enabled.

profilingStartTimestamp

profilingStartTimestamp(): number

Return a profiling start timestamp that mirrors the historic #PROFILE_T0 helper.

Returns: Millisecond timestamp representing the profiling start time.

readHighResolutionTime

readHighResolutionTime(): number

Obtain a monotonic-ish timestamp suitable for profiling.

Returns: Timestamp in milliseconds, preferring performance.now when available.

Example:

const timestamp = readHighResolutionTime();

resolveRngParameters

resolveRngParameters(): RngCacheParameters

Provide cached congruential parameters used by the shared fast RNG helper.

Returns: Immutable {@link RngCacheParameters} reference reused across draws.

Example:

const parameters = resolveRngParameters();

setDeterministicMode

setDeterministicMode(
  state: EngineState,
  seed: number | undefined,
): void

Enable deterministic RNG mode and optionally reseed the shared RNG state.

Parameters:

Returns: void.

evolutionEngine/scratchPools.ts

Scratch pool management helpers extracted from the maze evolution façade.

These utilities centralise the logic that grows and shrinks pooled buffers attached to the shared {@link EngineState}. They keep the façade lean by encapsulating heuristics for logits ring sizing, telemetry scratch sizing, and connection flag pooling.

allocateLogitsRing

allocateLogitsRing(
  capacity: number,
  actionDimension: number,
): Float32Array<ArrayBufferLike>[]

Build a non-shared logits ring sized to the requested capacity.

Parameters:

Returns: Array of typed rows representing the ring buffer.

ensureConnFlagsCapacity

ensureConnFlagsCapacity(
  state: EngineState,
  minimumCapacity: number,
): Int8Array<ArrayBufferLike> | null

Ensure the recurrent/gated detection bitmap has sufficient capacity.

Parameters:

Returns: Int8Array bitmap or null when allocation failed.

ensureLogitsRingCapacity

ensureLogitsRingCapacity(
  capacityRequest: LogitsRingCapacityOptions,
): LogitsRingCapacityResult

Grow or shrink the logits ring to accomodate the requested recent-step budget.

Parameters:

Returns: The resulting capacity and whether shared-array mode stayed enabled.

ensureScratchCapacity

ensureScratchCapacity(
  state: EngineState,
  request: ScratchCapacityRequest,
): void

Grow pooled scratch buffers to conservative sizes for the upcoming run.

Parameters:

initialiseSharedLogitsRing

initialiseSharedLogitsRing(
  state: EngineState,
  config: SharedLogitsConfig,
): boolean

Attempt to allocate SharedArrayBuffer-backed logits ring storage.

Parameters:

Returns: true when shared mode was activated successfully.

LogitsRingCapacityOptions

Shape describing the parameters used when ensuring the logits ring capacity.

LogitsRingCapacityResult

Result returned after resizing the logits ring.

maybeShrinkScratch

maybeShrinkScratch(
  state: EngineState,
  populationSize: number,
): void

Shrink oversized scratch buffers once the population size drops significantly.

Parameters:

ScratchCapacityRequest

Parameters describing the scratch sizing requirements for ensureScratchCapacity.

SharedLogitsConfig

Parameters passed when attempting to initialise the shared logits ring buffers.

evolutionEngine/setupHelpers.ts

Setup helpers for the ASCII maze evolution engine.

Responsibilities:

DashboardManagerLike

Dashboard manager shape for logging (optional log function).

FilesystemModule

Minimal filesystem module shape for type safety (Node.js fs module subset).

initPersistence

initPersistence(
  persistDir: string | undefined,
): { fs: FilesystemModule | null; path: PathModule | null; }

Initialize persistence helpers (Node fs & path) when available and ensure the target directory exists. This helper intentionally does nothing in browser-like hosts.

Steps:

  1. Detect whether a Node-like require is available and attempt to load fs and path
  2. If both modules are available and persistDir is provided, ensure the directory exists by creating it recursively when necessary
  3. Return an object containing the (possibly null) { fs, path } references for callers to use

Notes:

Parameters:

Returns: Object with { fs, path } where each value may be null when unavailable.

Example:

const { fs, path } = initPersistence('./snapshots'); if (fs && path) { fs.writeFileSync(path.join(dir, 'snapshot.json'), data); }

isPauseRequested

isPauseRequested(
  hostAdapter: EvolutionHostAdapter | undefined,
): boolean

Read host-controlled pause state without letting host errors break the engine.

Parameters:

Returns: True when the host asks the engine to remain paused.

makeFlushToFrame

makeFlushToFrame(
  hostAdapter: EvolutionHostAdapter | undefined,
): () => Promise<void>

Create a cooperative frame-yielding function used by the evolution loop.

Behaviour:

Steps:

  1. Choose the preferred tick function based on the host runtime
  2. When called, await the preferred tick; if the host adapter reports pause, poll again after the tick
  3. Resolve once a tick passed while not paused

Parameters:

Returns: A function that yields cooperatively to the next animation frame / tick.

Example:

const flushToFrame = makeFlushToFrame(); await flushToFrame(); // yields to next frame/tick

makeSafeWriter

makeSafeWriter(
  dashboardManager: DashboardManagerLike | undefined,
): (msg: string) => void

Build a resilient writer that attempts to write to Node stdout, then a provided dashboard logger, and finally console.log as a last resort.

Steps:

  1. If Node process.stdout.write is available, use it (no trailing newline forced)
  2. Else if dashboardManager.logFunction exists, call it
  3. Else fall back to console.log and trim the message

Notes:

Parameters:

Returns: A function accepting a single string message to write.

Example:

const safeWrite = makeSafeWriter(dashboardManager); safeWrite('[INFO] Generation 42 complete\n');

PathModule

Minimal path module shape for type safety (Node.js path module subset).

evolutionEngine/curriculumPhase.ts

hasMazeEvolutionReachedCurriculumThreshold

hasMazeEvolutionReachedCurriculumThreshold(
  progress: unknown,
  minProgressToPass: number,
): boolean

Determine whether a completed phase should advance the surrounding curriculum.

Parameters:

Returns: Whether the phase counts as curriculum-complete.

refineMazeEvolutionCarryOverNetwork

refineMazeEvolutionCarryOverNetwork(
  bestNetwork: INetwork | undefined,
  previousBestNetwork: INetwork | undefined,
): INetwork | undefined

Refine the winning network before seeding the next curriculum phase.

Parameters:

Returns: Refined winner or the best available carry-over network.

resolveMazeEvolutionPhaseOutcome

resolveMazeEvolutionPhaseOutcome(
  evolutionResult: MazeEvolutionRunResult,
  previousBestNetwork: INetwork | undefined,
  minProgressToPass: number,
): MazeEvolutionCurriculumPhaseOutcome

Resolve the stable curriculum-facing outcome of one maze evolution phase.

Parameters:

Returns: Shared curriculum outcome describing progress, solve state, and next carry-over seed.

Example:

const phaseOutcome = resolveMazeEvolutionPhaseOutcome(result, previousBest, 95);
if (phaseOutcome.solved) {
  previousBest = phaseOutcome.nextBestNetwork;
}

evolutionEngine/optionsAndSetup.ts

Options and Setup Module

Purpose:

Provides utilities for normalizing run options, preparing maze environment, and orchestrating NEAT driver creation with population seeding.

This module encapsulates:

ES2023 Policy:

createAndSeedNeat

createAndSeedNeat(
  opts: NormalizedRunOptions,
  inputSize: number,
  outputSize: number,
  fitnessContext: IFitnessEvaluationContext,
  scratchPopClone: default[],
  scratchSample: unknown[],
): CreateAndSeedNeatResult

Create and seed a NEAT driver with normalized configuration and optional initial population.

This function orchestrates the complete NEAT setup workflow:

  1. Build a fitness callback bound to the fitness context
  2. Instantiate the NEAT driver with normalized options
  3. Seed the driver's population from optional initial networks
  4. Warm up pooled scratch buffers to reduce first-use allocation spikes

Design Rationale:

Buffer Management:

Parameters:

Parameters:

Returns: Object containing:

Example:

// Create and seed NEAT with optional initial population const { neat, scratchPopClone, scratchSample } = createAndSeedNeat( normalizedOpts, 6, 4, fitnessContext, scratchPopCloneBuffer, scratchSampleBuffer );

normalizeRunOptions

normalizeRunOptions(
  options: IRunMazeEvolutionOptions,
  setDeterministic: (seed: number) => void,
  setReducedTelemetry: (enabled: boolean) => void,
  setMinimalTelemetry: (enabled: boolean) => void,
  setDisableBaldwin: (disabled: boolean) => void,
): NormalizedRunOptions

Normalize and validate run options with sensible defaults.

This function accepts the user-provided run options and returns a normalized options object with all defaults applied and configuration groups extracted.

Configuration Philosophy:

Default Values:

Side Effects:

Parameters:

Parameters:

Returns: Normalized options object with all defaults applied and configuration groups extracted

Example:

// Normalize with custom population size and deterministic mode const opts = normalizeRunOptions( { evolutionAlgorithmConfig: { popSize: 200, deterministic: true } }, (seed) => EvolutionEngine.setDeterministic(seed), (enabled) => EvolutionEngine.setReducedTelemetry(enabled), (enabled) => EvolutionEngine.setMinimalTelemetry(enabled), (disabled) => EvolutionEngine.setDisableBaldwin(disabled) );

prepareEnvironmentForRun

prepareEnvironmentForRun(
  opts: NormalizedRunOptions,
  scratchBundle: ScratchBundle,
): PreparedRunEnvironment

Prepare maze encoding, start/exit positions, distance map, and fitness context for the run.

This function accepts normalized options and returns a bundle of precomputed artifacts consumed by the evolution loop: encoded maze, start/exit coords, distance map, fixed I/O sizes, and a compact fitness context.

Design Rationale:

Fixed I/O Sizes:

Steps (high-level):

  1. Resolve maze source from opts (top-level maze or nested mazeConfig.maze)
  2. Encode the maze into a simulator-friendly representation
  3. Locate start ('S') and exit ('E') coordinates
  4. Build a distance map from the exit to speed simulations
  5. Assemble fixed I/O sizes and a fitnessContext object and return everything

Parameters:

Parameters:

Returns: Object containing:

Example:

// Prepare environment for a maze evolution run const env = prepareEnvironmentForRun(normalizedOpts, engineState.scratch); const neat = createAndSeedNeat(normalizedOpts, env.inputSize, env.outputSize, env.fitnessContext);

evolutionEngine/telemetryMetrics.ts

Telemetry logging and metric computation helpers extracted from the ASCII maze evolution engine.

The helpers in this module operate on the shared {@link EngineState} scratch buffers to avoid per-call allocations while keeping the main façade lighter. All telemetry is best-effort: any internal error is swallowed so that logging never impacts the evolution loop.

ActionEntropyStats

Structure describing the result of action-entropy computation.

collectTelemetryTail

collectTelemetryTail(
  state: EngineState,
  neat: unknown,
  tailLength: number,
): unknown

Collect a short telemetry tail from a NEAT instance when available.

Parameters:

Returns: Tail array, raw telemetry value or undefined on missing API/errors.

GenerationResult

Minimal structure representing a generation evolution result. Expected to have a path property containing the movement history.

LOG_TAG_ACTION_ENTROPY

Telemetry tag emitted when logging action-entropy statistics.

Example:

safeWrite(${LOG_TAG_ACTION_ENTROPY} gen=1 entropyNorm=0.500 uniqueMoves=4 pathLen=32\n);

LOG_TAG_LOGITS

Telemetry tag emitted when logging logits statistics and collapse diagnostics.

Example:

safeWrite(${LOG_TAG_LOGITS} gen=3 means=0.001,0.002,-0.001,-0.002 stds=0.01,0.02,0.03,0.04 kurt=0,0,0,0 entMean=0.500 stability=0.750 steps=32\n);

LOG_TAG_OUTPUT_BIAS

Telemetry tag emitted when logging bias statistics for output nodes.

Example:

safeWrite(${LOG_TAG_OUTPUT_BIAS} gen=2 mean=0.001 std=0.010 biases=0.01,-0.02,0.03,-0.01\n);

logActionEntropy

logActionEntropy(
  __0: LogActionEntropyParams,
): void

Emit a best-effort telemetry line containing action-entropy statistics.

Parameters:

Returns: void

LogActionEntropyParams

Parameters required to emit action-entropy telemetry.

logDiversity

logDiversity(
  __0: LogDiversityParams,
): void

Emit diversity telemetry including species count, Simpson index and weight standard deviation.

Parameters:

Returns: void

LogDiversityParams

Parameters required to emit population diversity telemetry.

logExploration

logExploration(
  __0: LogExplorationParams,
): void

Emit exploration telemetry summarising unique coverage, path length and ratios.

Parameters:

Returns: void

LogExplorationParams

Parameters required to emit exploration telemetry.

logGenerationTelemetry

logGenerationTelemetry(
  engineState: EngineState,
  neat: default,
  fittest: default | undefined,
  genResult: GenerationResult | undefined,
  generationIndex: number,
  writeLog: (msg: string) => void,
  actionDimension: number,
  recentWindow: number,
  reducedTelemetry: boolean,
  telemetryMinimal: boolean,
  onCollapseRecovery: () => void,
  isProfilingDetailsEnabledFn: (state: EngineState) => boolean,
  profilingStartTimestampFn: (state: EngineState) => number,
  accumulateProfilingDurationFn: (state: EngineState, label: string, duration: number) => void,
): void

Log comprehensive telemetry for a completed generation.

This orchestrator function coordinates all telemetry logging for a generation, including action entropy, output biases, logits statistics, exploration metrics, and diversity metrics. It respects the minimal telemetry mode and performs optional profiling when enabled.

Design Rationale:

Telemetry Steps (in order):

  1. Action entropy - normalized entropy and unique move statistics
  2. Output bias statistics - mean, std, and individual biases
  3. Logits statistics - means, stds, kurtosis, entropy, stability, collapse detection
  4. Exploration metrics - distinct coordinates visited and progress
  5. Diversity metrics - species richness, Simpson index, weight variance

Parameters:

Parameters:

Example:

// Log telemetry for generation 42 logGenerationTelemetry( state, neat, fittestNetwork, result, 42, console.log, 4, 40, false, false, () => antiCollapseRecovery(...), isProfilingDetailsEnabled, profilingStartTimestamp, accumulateProfilingDuration );

LogitStatsParams

Input parameters for computing logit statistics.

LogitStatsResult

Structure describing aggregated logit statistics.

logLogitsAndCollapse

logLogitsAndCollapse(
  __0: LogLogitsParams,
): void

Emit logits-level telemetry, detect collapse streaks and trigger anti-collapse recovery when needed.

Parameters:

Returns: void

LogLogitsParams

Parameters required to emit logits statistics, perform collapse detection and trigger recovery.

LogOutputBiasParams

Parameters required to emit output-bias telemetry.

logOutputBiasStats

logOutputBiasStats(
  __0: LogOutputBiasParams,
): void

Emit bias statistics for the fittest network's output nodes.

Parameters:

Returns: void

TelemetryBaseParams

Parameters shared by telemetry helpers that require access to the shared state and writer.

TelemetryWriter

TelemetryWriter(
  message: string,
): void

Writer signature reused across telemetry helpers.

evolutionEngine/neatConfiguration.ts

NEAT Configuration Module

Purpose:

Provides utilities for instantiating and seeding NEAT (NeuroEvolution of Augmenting Topologies) instances with standardized configuration and optional initial populations.

This module encapsulates:

ES2023 Policy:

createNeat

createNeat(
  inputCount: number,
  outputCount: number,
  fitnessCallback: (net: default) => number,
  cfg: NeatConfig | undefined,
): default

Create a NEAT instance with normalized configuration and opinionated defaults.

This function encapsulates the NEAT driver instantiation logic with defensive configuration normalization. It applies sensible defaults for population size, mutation operators, elitism, provenance, and various evolutionary strategies.

Configuration Philosophy:

Default Constants (from EvolutionEngine):

Parameters:

Parameters:

Returns: Configured Neat instance ready for evolution

Examples:

// Create a NEAT instance with custom population size and disabled lineage tracking const neat = createNeat(10, 4, fitnessFn, { popSize: 200, lineageTracking: false });

// Create with default configuration const neat = createNeat(10, 4, fitnessFn);

NeatConfig

NEAT configuration object shape for type safety.

seedInitialPopulation

seedInitialPopulation(
  neat: default,
  initialPopulation: default[] | undefined,
  initialBestNetwork: default | undefined,
  targetPopSize: number,
  scratchPopClone: default[],
): default[]

Seed the NEAT population from an optional initial population and/or an optional initial best network.

This function is intentionally best-effort and non-throwing: any cloning or driver mutating errors are swallowed so the evolution loop can continue.

Design Rationale:

Population Seeding Strategy:

  1. When initialPopulation is provided, clone all networks into pooled buffer
  2. Grow pooled buffer only when necessary (reuse across calls)
  3. When initialBestNetwork is provided, place clone at index 0
  4. Synchronize neat.options.popsize with actual population length

Pooling Pattern:

Parameters:

Parameters:

Returns: The scratchPopClone buffer (may be a new array if grown)

Examples:

// Seed with a provided population and ensure neat.options.popsize is kept in sync const pool = seedInitialPopulation(neat, providedPopulation, providedBest, 150, pool);

// Seed with only a best network (population will be empty except index 0) const pool = seedInitialPopulation(neat, undefined, bestNetwork, 150, pool);

evolutionEngine/networkInspection.ts

Network Inspection Module

Purpose:

Provides utilities for inspecting and analyzing neural network topology, including node classification, activation function detection, and connection analysis (recurrent/gated detection).

This module encapsulates:

ES2023 Policy:

printNetworkStructure

printNetworkStructure(
  engineState: EngineState,
  network: INetwork,
): void

Print a structured summary of network topology to the console.

This is a developer-facing inspection utility that logs:

Design:

Parameters:

Example:

printNetworkStructure(state, bestNetwork);

swallowError

swallowError(
  error: unknown,
): void

Utility to explicitly mark swallowed errors for lint compliance.

This function is used to explicitly void error objects in catch blocks to satisfy linters that flag unused catch binding variables. It has no runtime effect beyond the void operation.

Design Rationale:

Parameters:

Example:

try { riskyOperation(); } catch (error) { swallowError(error); // Explicit void for lint compliance }

evolutionEngine/populationPruning.ts

Population pruning and network warm-start helpers for the ASCII maze evolution engine.

This module centralizes genome connection pruning (simplification phase), output bias maintenance, and warm-start initialization logic. All helpers operate on the shared {@link EngineState} scratch buffers to avoid per-call allocations while keeping pruning logic modular and testable.

Responsibilities:

  1. Apply simplify-phase pruning to entire populations via strategy-driven connection disabling.
  2. Collect, sort, and selectively disable weak connections based on absolute weight.
  3. Initialize compass warm-start wiring for directional inputs.
  4. Re-center and clamp output node biases to prevent drift.
  5. Provide allocation-light helpers that reuse pooled buffers (connection candidates, node indices).

All functions are best-effort: internal errors are swallowed to avoid destabilizing the evolution loop.

applyCompassWarmStart

applyCompassWarmStart(
  __0: ApplyCompassWarmStartParams,
): void

Warm-start wiring for compass and directional openness inputs.

Steps:

  1. Validate network structure and extract node/connection arrays.
  2. Collect input and output node indices using the shared helper.
  3. For each of the 4 compass directions, ensure an input→output connection exists with light initialization.
  4. Connect the special 'compass' input (index 0) to all outputs with deterministic base weights.

Parameters:

Returns: void

Example:

applyCompassWarmStart({ state: sharedState, network: trainedNetwork });

ApplyCompassWarmStartParams

Parameters for warm-starting compass wiring.

ApplySimplifyPruningParams

Parameters for applying simplify pruning to a population.

applySimplifyPruningToPopulation

applySimplifyPruningToPopulation(
  __0: ApplySimplifyPruningParams,
): void

Apply simplify-phase pruning to the entire population.

Steps:

  1. Validate inputs and normalize prune fraction to [0, 1].
  2. Iterate through each genome in the population.
  3. Delegate per-genome pruning to the shared helper with isolated error handling.

Parameters:

Returns: void

Example:

applySimplifyPruningToPopulation({ neat: neatInstance, simplifyStrategy: 'weakRecurrentPreferred', simplifyPruneFraction: 0.15, });

centerOutputBiases

centerOutputBiases(
  __0: CenterOutputBiasesParams,
): void

Re-center and clamp output node biases to prevent drift.

Steps:

  1. Collect output node indices using the shared helper.
  2. Compute mean and standard deviation of output biases using Welford's online algorithm.
  3. Subtract the mean from each bias (re-centering) and clamp to ±OUTPUT_BIAS_CLAMP.
  4. Persist statistics for optional telemetry.

Parameters:

Returns: void

Example:

centerOutputBiases({ state: sharedState, network: trainedNetwork });

CenterOutputBiasesParams

Parameters for re-centering output biases.

evolutionEngine/trainingWarmStart.ts

trainingWarmStart.ts

Lamarckian warm-start and population pretraining subsystem.

This module is the part of the ASCII Maze engine that briefly steps outside pure neuroevolution and asks a pragmatic question: can a small amount of supervised guidance give the population a better starting shape before the main search pressure takes over again?

Responsibilities:

The important design constraint is restraint. These helpers are not trying to solve the maze with backprop. They are trying to give evolution a better first draft while preserving the example's core identity as an evolutionary system.

adjustOutputBiasesAfterTraining

adjustOutputBiasesAfterTraining(
  network: default,
  state: EngineState,
  constants: { DEFAULT_STD_SMALL: number; DEFAULT_STD_ADJUST_MULT: number; },
  scratchNodeIdx: Int32Array<ArrayBufferLike>,
  getNodeIndicesByType: NodeIndexCollector,
): void

Adjust output node biases after training to maintain exploration diversity.

This heuristic exists because short supervised bursts can make the action head too confident too early. By re-centering output biases and nudging very low-variance heads back outward, the engine keeps exploration pressure alive after warm-start training instead of letting one action dominate forever.

Steps:

  1. Collect output node biases into scratch buffer
  2. Compute mean and standard deviation via Welford's one-pass algorithm
  3. Subtract mean from each bias
  4. If std is very small, apply multiplicative boost to increase variance
  5. Clamp adjusted biases to safe operational range [-5, 5] and write back

Parameters:

Example:

adjustOutputBiasesAfterTraining( trainedNetwork, engineState, { DEFAULT_STD_SMALL: 0.05, DEFAULT_STD_ADJUST_MULT: 1.5 }, scratchIndexBuffer, getNodeIndicesByType );

applyLamarckianTraining

applyLamarckianTraining(
  neat: default,
  trainingSet: LamarckianTrainingCase[],
  iterations: number,
  sampleSize: number | undefined,
  safeWrite: (msg: string) => void,
  profileEnabled: boolean,
  completedGenerations: number,
  state: EngineState,
  constants: { DEFAULT_TRAIN_ERROR: number; DEFAULT_TRAIN_RATE: number; DEFAULT_TRAIN_MOMENTUM: number; DEFAULT_TRAIN_BATCH_SMALL: number; },
  adjustOutputBiases: WarmStartNetworkCallback,
): number

Apply Lamarckian backpropagation training to the entire population with optional profiling.

Runs a bounded supervised training pass on each network in the population, optionally downsampling the training set for efficiency. The intent is to "tilt" the policy landscape toward obviously sensible moves before the main evolutionary loop takes over, while still measuring and logging enough to see whether the warm-start is becoming too aggressive or too weak.

Steps:

  1. Validate inputs & early exits
  2. Start profiling timer if requested
  3. Optionally down-sample the training set (with replacement) to reduce cost for large sets
  4. Iterate networks performing a bounded training pass with softmax cross-entropy cost
  5. Apply post-training bias adjustment to each network
  6. Collect optional training stats (gradient norms)
  7. Emit aggregate gradient telemetry if samples were collected
  8. Return elapsed time when profiling; otherwise return 0

Parameters:

Returns: Elapsed milliseconds spent in training when profiling is enabled; otherwise 0.

Example:

const elapsed = applyLamarckianTraining( neatInstance, trainingExamples, 2, 8, console.log, true, currentGen, engineState, trainingConstants, adjustOutputBiasesAfterTraining );

buildLamarckianTrainingSet

buildLamarckianTrainingSet(
  state: EngineState,
  constants: { TRAIN_OUT_PROB_HIGH: number; TRAIN_OUT_PROB_LOW: number; PROGRESS_MEDIUM: number; PROGRESS_STRONG: number; PROGRESS_JUNCTION: number; PROGRESS_FOURWAY: number; PROGRESS_REGRESS: number; PROGRESS_MIN_SIGNAL: number; PROGRESS_MILD_REGRESS: number; DEFAULT_JITTER_PROB: number; AUGMENT_JITTER_BASE: number; AUGMENT_JITTER_RANGE: number; AUGMENT_PROGRESS_JITTER_PROB: number; AUGMENT_PROGRESS_DELTA_RANGE: number; AUGMENT_PROGRESS_DELTA_HALF: number; RNG_PARAMETERS: RngCacheParameters; },
): { input: number[]; output: number[]; }[]

Build the supervised training set used for Lamarckian warm-start training.

Generates a small curated dataset of canonical navigation scenarios combining:

Each training case maps a 6-dimensional input to a soft one-hot output:

Parameters:

Returns: Array of training cases { input: number[], output: number[] }.

Example:

const trainingSet = buildLamarckianTrainingSet(engineState, { TRAIN_OUT_PROB_HIGH: 0.9, TRAIN_OUT_PROB_LOW: 0.033, PROGRESS_MEDIUM: 0.5, // ... other constants });

LamarckianTrainingCase

Small supervised case used during Lamarckian warm-start.

NodeIndexCollector

NodeIndexCollector(
  nodes: NetworkNode[],
  nodeType: string,
): number

Callback used when a helper needs to write node indices of a specific role into scratch.

PretrainPopulationCallback

PretrainPopulationCallback(
  neat: default,
  trainingSet: LamarckianTrainingCase[],
): void

Population-wide warm-start hook used by the public orchestration helper.

pretrainPopulationWarmStart

pretrainPopulationWarmStart(
  neat: default,
  lamarckianTrainingSet: LamarckianTrainingCase[],
  constants: { PRETRAIN_MAX_ITER: number; PRETRAIN_BASE_ITER: number; DEFAULT_TRAIN_ERROR: number; DEFAULT_PRETRAIN_RATE: number; DEFAULT_PRETRAIN_MOMENTUM: number; DEFAULT_TRAIN_BATCH_SMALL: number; },
  applyCompassWarmStart: WarmStartNetworkCallback,
  centerOutputBiases: WarmStartNetworkCallback,
): void

Pretrain the population using a small supervised dataset and apply warm-start heuristics.

Behaviour & contract:

Steps:

  1. Validate inputs and obtain population (fast-exit on empty populations)
  2. For each network: guard missing train method, compute conservative iteration budget, then call train
  3. Apply warm-start heuristics (compass wiring + bias centering). Swallow any per-network exceptions.

Parameters:

Example:

pretrainPopulationWarmStart( neatInstance, trainingDataset, { PRETRAIN_MAX_ITER: 10, PRETRAIN_BASE_ITER: 3, ... }, applyCompassWarmStart, centerOutputBiases );

TrainableNetwork

The warm-start path trains ordinary runtime networks; this alias keeps intent explicit.

WarmStartNeatLike

Narrow NEAT view used by the warm-start helpers.

These helpers only need population access and a couple of option fields, so they avoid depending on the entire engine-facing driver surface.

WarmStartNetworkCallback

WarmStartNetworkCallback(
  network: default,
): void

Best-effort post-training hook applied to one network at a time.

warmStartPopulationIfNeeded

warmStartPopulationIfNeeded(
  neat: default,
  trainingSet: LamarckianTrainingCase[],
  state: EngineState,
  pretrainPopulation: PretrainPopulationCallback,
): void

Conditionally warm-start / pretrain the population using a provided training set.

Behaviour & contract:

Steps:

  1. Fast-guard invalid inputs – nothing to do when no data or driver
  2. Best-effort ensure pooled buffers exist and have capacity
  3. Invoke the pretrain helper (best-effort). This may mutate the NEAT driver

Parameters:

Example:

warmStartPopulationIfNeeded( neatDriver, trainingCasesArray, engineState, pretrainPopulationWarmStart );

evolutionEngine/populationDynamics.ts

populationDynamics.ts

Population-level dynamics for the ASCII Maze evolution loop.

This module owns the parts of the run that only make sense once the engine stops thinking about one genome at a time and starts thinking about the population as a living search process: plateau detection, simplify phases, parent selection, child creation, collapse recovery, and bulk connection cleanup.

Responsibilities:

The helpers here are intentionally allocation-light because they sit on the hot path of long runs. They lean on EngineState scratch buffers and accept slightly loose runtime surfaces so the public loop can stay orchestration- first while these internals adapt to the real Neat and Network instances.

antiCollapseRecovery

antiCollapseRecovery(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
  completedGenerations: number,
  safeWrite: (msg: string) => void,
  sampleSegmentIntoScratchFn: (state: EngineState, array: PopulationDynamicsGenome[], startIdx: number, count: number) => number,
): void

Reinitialize output biases and weights for anti-collapse recovery.

Behaviour:

Parameters:

Example:

antiCollapseRecovery(state, neat, genIndex, console.log, sampleSegmentIntoScratch);

applyMutationsToClone

applyMutationsToClone(
  state: EngineState,
  clone: PopulationDynamicsGenome,
  neat: PopulationDynamicsNeatLike,
  mutateCount: number,
): void

Apply up to mutateCount distinct mutation operations to clone.

Behaviour:

  1. Uses cached mutation operation array from getMutationOps.
  2. Selects up to mutateCount unique operations via partial Fisher–Yates shuffle.
  3. For small mutateCount values, uses unrolled fast path.

Parameters:

Example:

applyMutationsToClone(state, someClone, neat, 2);

compactGenomeConnections

compactGenomeConnections(
  genome: PopulationDynamicsGenome,
): number

Compact a single genome's connection list by removing disabled connections.

Behaviour:

Parameters:

Returns: Number of removed (disabled) connections.

Example:

const removed = compactGenomeConnections(genome);

compactPopulation

compactPopulation(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
): number

Compact entire population by removing disabled connections from each genome.

Behaviour:

Parameters:

Returns: Total removed disabled connections.

Example:

const totalRemoved = compactPopulation(state, neat);

createChildFromParent

createChildFromParent(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
  parent: PopulationDynamicsGenome,
): PopulationDynamicsGenome | undefined

Create a child genome from a parent via cloning and mutation.

Behaviour:

  1. Clone the parent genome (with or without ID tracking).
  2. Determine mutation count (1 or 2).
  3. Apply mutations to the clone.
  4. Register the clone with the NEAT driver.

Parameters:

Example:

createChildFromParent(state, neat, someParentGenome);

determineMutateCount

determineMutateCount(
  state: EngineState,
): number

Determine how many mutation operations to attempt (1 or 2).

Parameters:

Returns: 1 or 2 based on random sample.

ensureOutputIdentity

ensureOutputIdentity(
  neat: PopulationDynamicsNeatLike,
): void

Ensure all output nodes use identity activation.

Behaviour:

Parameters:

Example:

ensureOutputIdentity(neat);

expandPopulation

expandPopulation(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
  targetAdd: number,
  safeWrite: (msg: string) => void,
  completedGenerations: number,
): void

Expand the population by creating children from top-performing parents.

This is the engine's controlled answer to stagnation before full collapse recovery becomes necessary: widen the search, but bias that widening toward the current best-performing slice of the population instead of sampling from everyone equally.

Steps:

  1. Prepare working sets (population reference, sorted parent indices, parent pool size).
  2. Sample parents uniformly from the top parent pool.
  3. Create children via createChildFromParent (per-child failures ignored).
  4. Update neat.options.popsize and emit status line.

Parameters:

Example:

expandPopulation(state, neat, 10, msg => process.stdout.write(msg), currentGen);

getSortedIndicesByScore

getSortedIndicesByScore(
  state: EngineState,
  population: default[],
): number[]

Sort population indices by descending score using iterative quicksort.

Implementation details:

  1. Uses pooled scratch buffers (Int32Array or number[]) to avoid allocations.
  2. Initializes identity permutation [0,1,2,...].
  3. Sorts by descending population[idx].score with median-of-three pivot.
  4. Falls back to insertion sort for small partitions.

Parameters:

Returns: Sorted indices (highest score first). Empty array when input empty.

Example:

const indices = getSortedIndicesByScore(state, population);

handleSimplifyState

handleSimplifyState(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
  plateauCounter: number,
  plateauGenerations: number,
  simplifyDuration: number,
  simplifyMode: boolean,
  simplifyRemaining: number,
  simplifyStrategy: string,
  simplifyPruneFraction: number,
): { simplifyMode: boolean; simplifyRemaining: number; plateauCounter: number; }

Handle simplify entry and per-generation advance.

Behaviour:

Parameters:

Returns: Updated { simplifyMode, simplifyRemaining, plateauCounter }.

Example:

const state = handleSimplifyState(engineState, neat, 3, 10, 5, false, 0, 'aggressive', 0.2);

handleSpeciesHistory

handleSpeciesHistory(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
  speciesHistory: number[],
): boolean

Update species history and detect species collapse.

Behaviour:

Parameters:

Returns: true when collapse detected; false otherwise.

Example:

const collapsed = handleSpeciesHistory(state, neat, historyArray);

IndexBuffer

Typed or array-based index buffer for sorting

maybeExpandPopulation

maybeExpandPopulation(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
  dynamicPopEnabled: boolean,
  completedGenerations: number,
  dynamicPopMax: number,
  plateauGenerations: number,
  plateauCounter: number,
  dynamicPopExpandInterval: number,
  dynamicPopExpandFactor: number,
  dynamicPopPlateauSlack: number,
  safeWrite: (msg: string) => void,
): void

Attempt population expansion when conditions permit.

Behaviour:

Parameters:

Example:

maybeExpandPopulation(state, neat, true, 100, 500, 10, 8, 5, 0.1, 0.75, console.log);

maybeStartSimplify

maybeStartSimplify(
  plateauCounter: number,
  plateauGenerations: number,
  simplifyDuration: number,
): number

Decide whether to start a simplify phase based on plateau duration.

Behaviour:

Parameters:

Returns: Number of generations to run simplify (0 means "do not start").

Example:

const duration = maybeStartSimplify(plateauCount, 10, 5); if (duration > 0) { // Begin simplify for duration generations }

PopulationDynamicsConnection

Connection surface used when population helpers inspect network edges.

PopulationDynamicsGenome

Narrow genome view used by this module.

The real runtime objects are Network instances with a few extra evolution- time fields such as score, species, and lineage markers. Capturing that shape locally keeps the rest of the helper signatures readable without claiming that the entire engine only ever works with plain Network values.

PopulationDynamicsNeatLike

Narrow NEAT driver view used by this module.

The population-dynamics helpers care about population access, a handful of options, and mutation operator discovery. Everything else remains owned by the concrete Neat implementation.

PopulationDynamicsNode

Node surface used when population helpers inspect network topology.

PopulationDynamicsNoveltyConfig

Minimal novelty configuration surface used during anti-collapse escalation.

PopulationDynamicsOptions

Minimal NEAT options surface used by population-dynamics helpers.

PopulationDynamicsParentId

Runtime parent identifier carried through clone lineage tracking.

prepareExpansion

prepareExpansion(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
): { populationRef: PopulationDynamicsGenome[]; sortedIdx: number[]; parentPoolSize: number; }

Prepare working sets for population expansion.

The key idea is to sort once, derive a parent pool from the best quarter of the population, and then let child creation sample uniformly from that pool. That gives expansion a clear bias toward current evidence without hard-coding elitist cloning only.

Parameters:

Returns: Object with populationRef, sortedIdx, parentPoolSize.

Example:

const { populationRef, sortedIdx, parentPoolSize } = prepareExpansion(state, neat);

pruneSaturatedHiddenOutputs

pruneSaturatedHiddenOutputs(
  state: EngineState,
  genome: unknown,
  getNodeIndicesByType: (nodes: NetworkNode[], nodeType: string) => number,
  collectHiddenToOutputConns: (hiddenNode: NetworkNode, nodes: NetworkNode[], outputCount: number) => NetworkConnection[],
): void

Prune saturated hidden-to-output connections for a single genome.

Behaviour:

Parameters:

Example:

pruneSaturatedHiddenOutputs(state, genome, getNodeIndicesByTypeFn, collectHiddenToOutputConnsFn);

registerClone

registerClone(
  neat: PopulationDynamicsNeatLike,
  clone: PopulationDynamicsGenome,
  parentId: PopulationDynamicsParentId | undefined,
): void

Register a cloned genome with the NEAT driver's bookkeeping.

Parameters:

Example:

registerClone(neat, genomeClone, parentId);

reinitializeGenomeOutputsAndWeights

reinitializeGenomeOutputsAndWeights(
  state: EngineState,
  genome: unknown,
): { connReset: number; biasReset: number; }

Reinitialize output node biases and outgoing weights for a single genome.

Behaviour:

Parameters:

Returns: Object with { connReset: number, biasReset: number }.

Example:

const deltas = reinitializeGenomeOutputsAndWeights(state, genome);

runSimplifyCycle

runSimplifyCycle(
  state: EngineState,
  neat: PopulationDynamicsNeatLike,
  simplifyRemaining: number,
  simplifyStrategy: string,
  simplifyPruneFraction: number,
): number

Run a single simplify/pruning generation if conditions permit.

Conceptually, simplify mode is the engine saying: "stop growing for a moment and remove weak structure so the current population has to justify what it keeps." That is useful after a long plateau, but only in non-browser hosts where the extra pruning work is acceptable.

Steps:

  1. Normalize inputs and perform fast exits for zero remaining or invalid population.
  2. Environment gate: skip pruning in browser-like hosts.
  3. Record profiling start time when enabled.
  4. Execute pruning across the population (best-effort per-genome).
  5. Record profiling delta and return remaining generations decremented.

Parameters:

Returns: Remaining simplify generations after executing one cycle (0 means done).

Example:

const remaining = runSimplifyCycle(state, neat, 5, 'pruneWeak', 0.2);

updatePlateauState

updatePlateauState(
  fitness: number,
  lastBestFitnessForPlateau: number,
  plateauCounter: number,
  plateauImprovementThreshold: number,
): { plateauCounter: number; lastBestFitnessForPlateau: number; }

Update plateau state based on current fitness vs baseline.

Behaviour:

Parameters:

Returns: Updated { plateauCounter, lastBestFitnessForPlateau }.

Example:

const state = updatePlateauState(1.23, 1.1, 3, 0.05); // state => { plateauCounter: 0, lastBestFitnessForPlateau: 1.23 }

evolutionEngine/engineState.constants.ts

Constant values shared by the ASCII maze engine-state facade and its helper utilities.

ACTION_OUTPUT_DIMENSION

Number of action outputs (N, E, S, W) represented in each logits row.

DEFAULT_CONNECTION_FLAG_CAPACITY

Default capacity reserved for connection flag buffers.

DEFAULT_HISTORY_BUFFER_CAPACITY

Default capacity reused by history and sampling scratch arrays.

DEFAULT_LOGITS_RING_CAPACITY

Default logits ring length used when allocating pooled softmax buffers.

DEFAULT_NODE_INDEX_BUFFER_CAPACITY

Default capacity for the node index buffer used during inspection.

DEFAULT_QUICKSORT_STACK_CAPACITY

Default stack depth reserved for quicksort range storage.

DEFAULT_RNG_CACHE_BATCH_SIZE

Default RNG cache batch size mirroring the façade constant.

DEFAULT_SAMPLE_POOL_SIZE

Default pool size for telemetry sampling helpers.

DEFAULT_SMALL_EXPLORE_TABLE_CAPACITY

Default capacity for the small exploration table scratch.

DEFAULT_SORTED_INDEX_CAPACITY

Default capacity reserved for sorted index scratch arrays.

DEFAULT_SPECIES_SCRATCH_CAPACITY

Default capacity reserved for species identifier scratch arrays.

DEFAULT_STRING_BUFFER_CAPACITY

Default capacity for telemetry string assembly buffers.

DEFAULT_VISITED_HASH_LOAD_FACTOR

Default load factor target for the visited coordinate hash table.

MAX_VISITED_HASH_LOAD_FACTOR

Maximum safe load factor applied when normalising visited-hash configuration.

MIN_VISITED_HASH_LOAD_FACTOR

Minimum safe load factor applied when normalising visited-hash configuration.

RNG_GOLDEN_RATIO_SEED

Knuth-derived 32-bit constant used when seeding the RNG state.

evolutionEngine/evolutionEngine.constants.ts

Stable tuning values consumed by the ASCII maze evolution facade.

The public EvolutionEngine class should read as orchestration-first code. These constants live in a dedicated module so warm-start tuning, loop thresholds, and shared fallback arrays do not crowd the facade itself.

EVOLUTION_ENGINE_ACTION_DIMENSION

Number of action outputs emitted by the ASCII maze policy network.

Example:

console.log(EVOLUTION_ENGINE_ACTION_DIMENSION); // 4

EVOLUTION_ENGINE_EMPTY_VECTOR

Shared empty vector reused when engine helpers need a stable array fallback.

Example:

const outgoing = node.connections?.out ?? EVOLUTION_ENGINE_EMPTY_VECTOR;

EVOLUTION_ENGINE_INITIAL_LOGITS_RING_CAPACITY

Initial ring-buffer capacity used for logits telemetry.

The ring may grow at runtime, but the facade starts from this size so the first generations stay allocation-light.

EVOLUTION_ENGINE_LOOP_CONSTANTS

Main-loop tuning values passed into the extracted evolution-loop helpers.

This table keeps the public facade declarative while preserving the same runtime thresholds and training behaviour.

EVOLUTION_ENGINE_MAX_LOGITS_RING_CAPACITY

Hard safety limit for the logits telemetry ring-buffer capacity.

EVOLUTION_ENGINE_PRETRAIN_CONSTANTS

Pretraining controls used by the Lamarckian warm-start helpers.

Example:

const iterations = EVOLUTION_ENGINE_PRETRAIN_CONSTANTS.PRETRAIN_MAX_ITER;

EVOLUTION_ENGINE_WARM_START_CONSTANTS

Warm-start curriculum samples used to bias early supervised guidance.

These values shape the synthetic targets used before the main NEAT loop takes over, so they are grouped here instead of being scattered across the facade method body.

evolutionEngine/engineState.utils.ts

Pure utility helpers shared by the ASCII maze engine-state facade.

buildTelemetryHandles

buildTelemetryHandles(
  scratch: EngineScratchState,
): TelemetryScratchHandles

Build typed handles referencing the ensured telemetry scratch buffers.

Parameters:

Returns: Structured handles consumed by telemetry helpers.

createLogitsRing

createLogitsRing(): Float32Array<ArrayBufferLike>[]

Build a logits ring sized to the requested default capacity.

Returns: Array of Float32Array rows sized to the action dimension.

createProfilingAccumulators

createProfilingAccumulators(): Record<string, number>

Build a fresh profiling accumulator map seeded with zero totals.

Returns: Accumulator record keyed by profiling segment name.

createSnapshotReusableObject

createSnapshotReusableObject(): { generation: number; bestFitness: number; simplifyMode: boolean; plateauCounter: number; timestamp: number; telemetryTail: undefined; top: undefined; }

Build the reusable snapshot metadata payload consumed by persistence helpers.

Returns: Snapshot placeholder populated with neutral defaults.

ensureTelemetryFloatPools

ensureTelemetryFloatPools(
  scratch: EngineScratchState,
  hints: TelemetryCapacityHints,
): void

Ensure all Float64 scratch pools required for telemetry are adequately sized.

Parameters:

Returns: void.

ensureTelemetryStringBuffer

ensureTelemetryStringBuffer(
  buffer: string[],
  required: number,
): string[]

Ensure the reusable string assembly buffer has sufficient capacity.

Parameters:

Returns: Original buffer when large enough, otherwise a grown copy.

nextPowerOfTwo

nextPowerOfTwo(
  candidate: number,
): number

Compute the next power-of-two for geometric growth.

Parameters:

Returns: Smallest power-of-two greater than or equal to the candidate.

normaliseRngBatchSize

normaliseRngBatchSize(
  requestedBatchSize: number,
): number

Clamp the RNG cache batch size to a positive integer.

Parameters:

Returns: Valid batch size.

normaliseRngSeed

normaliseRngSeed(
  rawSeed: number,
): number

Normalise a raw numeric seed into an unsigned 32-bit value.

Parameters:

Returns: Unsigned 32-bit seed suitable for the congruential generator.

normaliseTelemetryCapacityHints

normaliseTelemetryCapacityHints(
  request: TelemetryScratchRequest,
): TelemetryCapacityHints

Derive normalised capacity hints from the raw telemetry scratch request.

Parameters:

Returns: Sanitised capacity values used during buffer initialisation.

normaliseVisitedHashEntries

normaliseVisitedHashEntries(
  requestedEntries: number,
): number

Clamp the requested target entry count to a non-negative integer.

Parameters:

Returns: Sanitised entry count used for capacity planning.

normaliseVisitedHashLoad

normaliseVisitedHashLoad(
  requestedLoadFactor: number,
): number

Ensure the visited hash load factor falls within a sensible range.

Parameters:

Returns: Clamped load factor with a fallback to the project default.

resolveProfilingEnabled

resolveProfilingEnabled(): boolean

Compute whether detailed profiling is enabled via the environment flag.

Returns: True when the profiling environment flag is set.

evolutionEngine/evolutionEngine.utils.ts

collectEvolutionEngineHiddenToOutputConnections

collectEvolutionEngineHiddenToOutputConnections(
  state: EngineState,
  hiddenNode: NetworkNode,
  nodes: NetworkNode[],
  outputCount: number,
): NetworkConnection[]

Collect enabled outgoing connections from one hidden node into the shared scratch array.

The evolution loop reuses this helper during network inspection to avoid allocating transient arrays while still keeping the public facade compact.

Parameters:

Returns: Shared scratch array containing enabled hidden-to-output connections.

Example:

const connections = collectEvolutionEngineHiddenToOutputConnections( state, hiddenNode, nodes, outputCount, );

collectEvolutionEngineNodeIndicesByType

collectEvolutionEngineNodeIndicesByType(
  state: EngineState,
  nodes: NetworkNode[] | undefined,
  type: string,
): number

Collect node indices of one requested type into the shared engine scratch buffer.

This helper keeps the facade free of buffer-growth details while preserving the existing allocation-light behaviour used by the evolution loop.

Parameters:

Returns: Number of matching indices written into the shared scratch buffer.

Example:

const outputCount = collectEvolutionEngineNodeIndicesByType(state, nodes, 'output');

Generated from source JSDoc • GitHub