architecture/network/genetic

Network-level crossover boundary for recombining two compatible parent graphs into one offspring.

This chapter is the genetic shelf of architecture/network/: the place where structure is inherited rather than mutated from scratch. It answers a practical question that shows up during neuroevolution and testing alike: if two networks already expose the same input and output contract, how should a child network mix their node and connection genes without losing a runnable topology?

The helpers below split that answer into setup, genome-backed selection, and materialization. Setup decides how large the offspring can be and which parent has inheritance priority. The genome heredity boundary chooses overlapping and disjoint genes from strict structural contracts. Materialization turns the chosen genes back into a concrete Network instance and restores gating when the chosen gater still exists. That keeps the public crossOver() surface compact while the README can still teach the full inheritance flow.

flowchart LR
  ParentA[Parent A] --> Context[Build crossover context]
  ParentB[Parent B] --> Context
  Context --> Nodes[Assign offspring nodes]
  Nodes --> Genes[Choose connection genes]
  Genes --> Offspring[Materialize offspring network]

Required teaching output: old crossover vs proper crossover.

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;

  subgraph Old[Legacy mental model, approximation]
    direction TB
    oldA[Parent A connections]:::base --> oldKey[Key by endpoint index pair\nfrom index, to index]:::accent
    oldB[Parent B connections]:::base --> oldKey
    oldKey --> oldMatch["Match" if endpoints look the same]:::base
  end

  subgraph Proper[Proper-NEAT alignment, historical markings]
    direction TB
    newA[Parent A connections]:::base --> innov[Key by innovation number\nconnection innovation]:::accent
    newB[Parent B connections]:::base --> innov
    innov --> newMatch[Match / disjoint / excess by innovation id]:::base
  end

  Old --> offs[Offspring gene set]:::base
  Proper --> offs

This chapter now aligns inherited connection genes by preserved innovation numbers instead of synthetic endpoint ids, which moves the runtime crossover boundary much closer to canonical NEAT historical markings. The remaining simplifications are now narrower: node inheritance still follows runtime slot ordering, and recurrent-policy enforcement still happens later during materialization.

For compact background reading on the wider idea, see Wikipedia contributors, Crossover (genetic algorithm). The implementation here specializes that idea to graph-shaped neural networks with gating and disabled genes.

Example: create one offspring using ordinary fitness-biased inheritance.

const child = crossOver(parentA, parentB);

Example: force symmetric inheritance when you want experimentation rather than fitter-parent bias.

const exploratoryChild = crossOver(parentA, parentB, true);

architecture/network/genetic/network.genetic.utils.ts

crossOver

crossOver(
  parentNetwork1: default,
  parentNetwork2: default,
  equal: boolean,
): default

NEAT-inspired crossover between two parent networks producing a single offspring.

Conceptual model:

Current simplifications relative to canonical NEAT:

Compatibility assumptions:

High-level algorithm:

  1. Validate that parents have identical I/O dimensionality (required for compatibility).
  2. Decide offspring node array length:
    • If equal flag set or scores tied: random length in [minNodes, maxNodes].
    • Else: length of fitter parent.
  3. For each index up to chosen size, pick a node gene from parents per rules:
    • Input indices: always from parent1 (assumes identical input interface).
    • Output indices (aligned from end): randomly choose if both present else take existing.
    • Hidden indices: if both present pick randomly; else inherit from fitter (or either if equal).
  4. Reindex offspring nodes.
  5. Delegate innovation-keyed connection-gene collection and inheritance choice to the genome heredity boundary.
  6. For matching genes (present in both parents with the same innovation), randomly choose one; if either copy is disabled, apply the explicit re-enable policy through the crossover RNG.
  7. For disjoint/excess genes, inherit only from the fitter parent (or from both parents when equal is enabled or scores tie).
  8. Rebuild the offspring node set from required IO nodes plus inherited gene identities, then materialize selected connection genes under the offspring topology intent.
  9. Reattach gating if gater node exists in offspring.

Enabled reactivation probability:

Parameters:

Returns: Offspring network instance.

Example:

const offspring = crossOver(parentA, parentB);
offspring.mutate();

crossOverWithRandomGenerator

crossOverWithRandomGenerator(
  parentNetwork1: default,
  parentNetwork2: default,
  equal: boolean,
  randomGenerator: RandomGenerator,
): default

Internal crossover entrypoint that uses an explicit RNG supplied by the NEAT evolve layer.

The public Network.crossOver() surface stays convenience-oriented so standalone callers can keep relying on the runtime-network fallback. The evolve controller uses this helper instead when crossover randomness must stay in the same deterministic stream as parent selection.

Parameters:

Returns: Offspring network instance.

architecture/network/genetic/network.genetic.setup.utils.ts

Crossover setup helpers for the network genetic boundary.

This file prepares the immutable context used by the rest of the crossover pipeline.

Key responsibilities:

This separation keeps the public crossover surface compact while giving the evolve controller one clear seam for deterministic replay.

asGeneticNetwork

asGeneticNetwork(
  network: default,
): GeneticNetwork

Coerces a network to the internal genetic runtime shape.

Parameters:

Returns: Network with runtime genetic properties.

assignNodeIndexes

assignNodeIndexes(
  nodes: default[],
): void

Assigns contiguous indices to a node list.

Parameters:

Returns: Nothing.

assignOffspringNodes

assignOffspringNodes(
  nodeContext: CrossoverNodeBuildContext,
): void

Builds and reindexes offspring nodes.

Parameters:

Returns: Nothing.

buildOffspringNodes

buildOffspringNodes(
  parent1: GeneticNetwork,
  parent2: GeneticNetwork,
  parentMetrics: ParentMetrics,
  offspringNodeCount: number,
  equal: boolean,
  randomGenerator: RandomGenerator,
): default[]

Builds the offspring node list by selecting genes per slot.

Parameters:

Returns: Cloned offspring node genes.

chooseOffspringConnectionGenes

chooseOffspringConnectionGenes(
  context: CrossoverContext,
): ConnectionGene[]

Chooses all offspring connection genes from both parents.

Innovation-aligned heredity selection now lives behind the strict genome boundary. This runtime seam now returns only the stable materialization descriptor consumed by the phenotype builder, without carrying parent-local node-index hints across the adapter.

Parameters:

Returns: Chosen connection genes.

cloneNodeGene

cloneNodeGene(
  sourceNode: default,
): default

Clones node structural gene attributes.

Historical node identity must survive crossover even though the offspring is rebuilt as a fresh runtime graph. Preserving geneId here lets later materialization resolve inherited connection endpoints by stable gene identity instead of by whatever transient slot the node lands in after reindexing.

Parameters:

Returns: Cloned node.

createCrossoverContext

createCrossoverContext(
  parentNetwork1: default,
  parentNetwork2: default,
  equal: boolean,
  injectedRandomGenerator: RandomGenerator | undefined,
): CrossoverContext

Creates the immutable crossover baseline context.

Parameters:

Returns: Initialized crossover context.

createNodeBuildContext

createNodeBuildContext(
  context: CrossoverContext,
): CrossoverNodeBuildContext

Creates the node-build context for offspring node selection.

Parameters:

Returns: Node-build context.

createOffspringScaffold

createOffspringScaffold(
  inputSize: number,
  outputSize: number,
  topologyIntent: NetworkTopologyIntent,
): GeneticNetwork

Creates an empty offspring scaffold with reset runtime arrays.

Parameters:

Returns: Initialized offspring runtime object.

createParentNodeRegions

createParentNodeRegions(
  nodes: default[],
): ParentNodeRegions

Partitions one runtime node list into canonical IO and hidden shelves.

Crossover setup still owns runtime node selection, but it must not assume the raw nodes[] array already keeps outputs at the tail. Structural edits can preserve a valid phenotype while drifting away from that canonical order. Reading through per-type partitions keeps output-slot inheritance stable without mutating the parent runtime graph.

Parameters:

Returns: Canonical node partitions.

determineOffspringNodeCount

determineOffspringNodeCount(
  equal: boolean,
  parentMetrics: ParentMetrics,
  randomGenerator: RandomGenerator,
): number

Determines offspring node count from fitness/equality policy.

Parameters:

Returns: Offspring node count.

getRandomGenerator

getRandomGenerator(
  parentNetwork: default,
): RandomGenerator

Resolves the random generator used by crossover decisions.

Parameters:

Returns: Random function.

resolveOffspringTopologyIntent

resolveOffspringTopologyIntent(
  parentNetwork1: default,
  parentNetwork2: default,
): NetworkTopologyIntent

Resolves the topology policy to preserve on the offspring scaffold.

A mixed-parent crossover should not silently downgrade recurrent-capable parents back to feed-forward mode, because that would prune valid inherited genes during materialization. The offspring therefore stays feed-forward only when both parents advertise the feed-forward contract.

Parameters:

Returns: Offspring topology intent.

resolveParentMetrics

resolveParentMetrics(
  parent1: GeneticNetwork,
  parent2: GeneticNetwork,
  outputSize: number,
): ParentMetrics

Computes common parent metrics reused across helper functions.

Parameters:

Returns: Parent metrics.

selectHiddenNodeGene

selectHiddenNodeGene(
  hiddenOrdinal: number,
  parent1NodeRegions: ParentNodeRegions,
  parent2NodeRegions: ParentNodeRegions,
  parentMetrics: ParentMetrics,
  equal: boolean,
  randomGenerator: RandomGenerator,
): default | undefined

Selects a hidden-region node gene.

Parameters:

Returns: Selected hidden node gene.

selectInputNodeGene

selectInputNodeGene(
  nodeIndex: number,
  parent1NodeRegions: ParentNodeRegions,
): default | undefined

Selects an input-region node gene.

Parameters:

Returns: Parent 1 input node gene.

selectNodeGeneAtIndex

selectNodeGeneAtIndex(
  nodeIndex: number,
  offspringNodeCount: number,
  parent1NodeRegions: ParentNodeRegions,
  parent2NodeRegions: ParentNodeRegions,
  parentMetrics: ParentMetrics,
  equal: boolean,
  randomGenerator: RandomGenerator,
): default | undefined

Selects a node gene for a specific offspring slot.

Parameters:

Returns: Selected parent node gene, when present.

selectOutputNodeGene

selectOutputNodeGene(
  outputOrdinal: number,
  parent1NodeRegions: ParentNodeRegions,
  parent2NodeRegions: ParentNodeRegions,
  randomGenerator: RandomGenerator,
): default | undefined

Selects an output-region node gene by interface ordinal.

Runtime parents can drift away from strict input-hidden-output ordering after structural edits. This runtime shelf therefore reads outputs from canonical per-type partitions rather than trusting the raw tail slots on nodes[].

Parameters:

Returns: Selected output node gene.

validateParentCompatibility

validateParentCompatibility(
  parentNetwork1: default,
  parentNetwork2: default,
): void

Validates parent compatibility for crossover.

Parameters:

Returns: Nothing.

architecture/network/genetic/network.genetic.selection.utils.ts

chooseConnectionGenes

chooseConnectionGenes(
  parent1: GeneticNetwork,
  parent2: GeneticNetwork,
  parentMetrics: ParentMetrics,
  equal: boolean,
  randomGenerator: RandomGenerator,
): ConnectionGene[]

Adapts genome-owned heredity selection back into runtime crossover genes.

Step 7.2b keeps this runtime shelf as one thin bridge that:

  1. projects each parent into the strict genome contract,
  2. asks the genome boundary which structural genes survive, and
  3. strips transient runtime node-index hints so the phenotype materializer reads only stable gene ids plus the inherited weight, enabled state, and innovation identity.

Runtime node scaffolding, topology pruning, and gating reattachment remain outside this adapter.

Parameters:

Returns: Chosen genes for runtime materialization.

architecture/network/genetic/network.genetic.materialize.utils.ts

addInheritedGeneIdWhenRequired

addInheritedGeneIdWhenRequired(
  requiredGeneIds: Set<number>,
  currentNodeOrderByGeneId: Map<number, number>,
  sourceNodesByGeneId: Map<number, MaterializationNodeSource>,
  geneId: number | null,
): void

Adds one gene id to the required-node set when it is defined.

Parameters:

Returns: Nothing.

applyConnectionGeneToConnection

applyConnectionGeneToConnection(
  connection: default,
  connectionGene: ConnectionGene,
): void

Applies gene properties to a runtime connection.

Parameters:

Returns: Nothing.

assignOffspringNodeIndexes

assignOffspringNodeIndexes(
  nodes: default[],
): void

Assigns contiguous node indices after rebuilt node ordering is finalized.

Parameters:

Returns: Nothing.

attachGaterIfAvailable

attachGaterIfAvailable(
  context: OffspringMaterializationContext,
  connection: default,
  gaterGeneId: number | null,
): void

Attaches a gater node when the inherited gater gene exists in the offspring.

Parameters:

Returns: Nothing.

buildMaterializedOffspringNodes

buildMaterializedOffspringNodes(
  currentOffspringNodes: default[],
  chosenGenes: ConnectionGene[],
  sourceNodesByGeneId: Map<number, MaterializationNodeSource>,
): default[]

Rebuilds the offspring node set from required IO nodes plus inherited genes.

Step 2.3 moves node survival away from the provisional slot count chosen during setup. If an inherited connection references a hidden node that the provisional scaffold omitted, this pass rehydrates that node by geneId before any connection materialization begins.

Parameters:

Returns: Rebuilt offspring node set.

cloneSourceNodeGene

cloneSourceNodeGene(
  sourceNode: default,
): default

Clones one fallback source node for materialization-time insertion.

Parameters:

Returns: Structural clone for the rebuilt offspring node set.

collectRequiredNodeGeneIds

collectRequiredNodeGeneIds(
  currentOffspringNodes: default[],
  chosenGenes: ConnectionGene[],
  sourceNodesByGeneId: Map<number, MaterializationNodeSource>,
): Set<number>

Collects the node gene ids that must survive materialization.

The offspring always preserves its IO interface, then adds any nodes named by inherited connection endpoints or gaters.

Parameters:

Returns: Required node gene ids.

compareMaterializedNodeCandidates

compareMaterializedNodeCandidates(
  leftNodeCandidate: MaterializedNodeCandidate,
  rightNodeCandidate: MaterializedNodeCandidate,
): number

Compares rebuilt node candidates using IO/hidden ordering first.

Parameters:

Returns: Relative sort order.

createConnectionForEndpoints

createConnectionForEndpoints(
  endpointsContext: GeneEndpointsContext,
): default | undefined

Creates a runtime connection for endpoint nodes.

Parameters:

Returns: Created connection or undefined.

createMaterializationContext

createMaterializationContext(
  targetOffspring: GeneticNetwork,
  chosenGenes: ConnectionGene[],
  sourceNetworks: readonly GeneticNetwork[],
): OffspringMaterializationContext

Creates the immutable top-level context used during materialization.

Parameters:

Returns: Materialization context.

createMaterializedNodeCandidate

createMaterializedNodeCandidate(
  geneId: number,
  currentOffspringNodes: default[],
  currentNodeOrderByGeneId: Map<number, number>,
  sourceNodesByGeneId: Map<number, MaterializationNodeSource>,
): MaterializedNodeCandidate | undefined

Creates one ordered node candidate for the rebuilt offspring node set.

Existing provisional offspring nodes keep their selected structural traits. Missing inherited nodes are cloned from the first parent/source that still exposes the required geneId.

Parameters:

Returns: Ordered node candidate when the gene id can be resolved.

createNodeOrderLookup

createNodeOrderLookup(
  nodes: default[],
): Map<number, number>

Creates a node-order lookup keyed by stable gene id.

Parameters:

Returns: Current node order by gene id.

createOffspringConnection

createOffspringConnection(
  offspring: GeneticNetwork,
  fromNode: default,
  toNode: default,
  connectionWeight: number,
): default | undefined

Creates a single offspring connection edge.

Parameters:

Returns: Created connection or undefined.

createOffspringNodeLookupByGeneId

createOffspringNodeLookupByGeneId(
  nodes: default[],
): Map<number, default>

Builds a stable node lookup keyed by gene id for one offspring scaffold.

Connection genes preserve historical endpoint identity even when the runtime node array is rebuilt in a different slot order. This map lets the materialization pass reattach inherited genes to the right runtime nodes.

Parameters:

Returns: Gene-id lookup map.

createSourceInterfaceOrdinalMap

createSourceInterfaceOrdinalMap(
  sourceNodeEntriesByGeneId: Map<number, MaterializationNodeSource>,
): Map<number, number>

Flattens source interface ordinals into a plain lookup keyed by gene id.

Parameters:

Returns: Plain source interface-ordinal lookup.

createSourceNodeLookupByGeneId

createSourceNodeLookupByGeneId(
  currentOffspringNodes: default[],
  sourceNetworks: readonly GeneticNetwork[],
): Map<number, MaterializationNodeSource>

Builds the fallback node-gene lookup used when chosen genes reference nodes not present in the provisional offspring scaffold.

Parameters:

Returns: Lookup of source node genes by stable gene id.

createSourceNodeMap

createSourceNodeMap(
  sourceNodeEntriesByGeneId: Map<number, MaterializationNodeSource>,
): Map<number, default>

Flattens source-node entries into a plain node lookup keyed by gene id.

Parameters:

Returns: Plain source node lookup.

createTraversalContexts

createTraversalContexts(
  context: OffspringMaterializationContext,
  genes: ConnectionGene[],
): GeneTraversalContext[]

Builds traversal contexts for each candidate gene.

Parameters:

Returns: Traversal contexts.

hasExistingProjection

hasExistingProjection(
  endpointsContext: GeneEndpointsContext,
): boolean

Checks whether the source endpoint already projects to the target endpoint.

Parameters:

Returns: True when projection already exists.

isEndpointsContextAllowedByTopologyPolicy

isEndpointsContextAllowedByTopologyPolicy(
  endpointsContext: GeneEndpointsContext,
): boolean

Validates one resolved gene against the offspring topology policy.

Feed-forward offspring prune self and backward genes explicitly. Unconstrained offspring keep those genes and let connect() register them in the runtime self/recurrent collections.

Parameters:

Returns: True when the gene is legal for the offspring topology intent.

isRequiredInterfaceNode

isRequiredInterfaceNode(
  node: default,
): boolean

Resolves whether one node is part of the required public IO contract.

Parameters:

Returns: True when the node is input or output.

materializeOffspringConnections

materializeOffspringConnections(
  offspring: GeneticNetwork,
  chosenGenes: ConnectionGene[],
  sourceNetworks: readonly GeneticNetwork[],
): void

Materializes selected connection genes in the offspring network.

The offspring node array is reindexed after node selection, so this runtime seam intentionally avoids parent-time node indexes altogether. This materialization pass first rebuilds the offspring node set from required IO nodes plus any hidden nodes referenced by the chosen genes, then resolves endpoints and gaters through a stable geneId lookup map. Feed-forward pruning is now derived from the offspring topology contract instead of any incidental parent ordering hint.

Parameters:

Returns: Nothing.

materializeSingleTraversalContext

materializeSingleTraversalContext(
  traversalContext: GeneTraversalContext,
): void

Materializes one eligible traversal context when no duplicate projection exists.

Parameters:

Returns: Nothing.

materializeTraversalContexts

materializeTraversalContexts(
  traversalContexts: GeneTraversalContext[],
): void

Materializes each eligible traversal context independently.

Parameters:

Returns: Nothing.

registerNodeSources

registerNodeSources(
  sourceNodesByGeneId: Map<number, MaterializationNodeSource>,
  nodes: default[],
  sourcePriority: number,
): void

Registers one ordered node list as a fallback source for gene-id lookup.

Parameters:

Returns: Nothing.

resolveGeneEndpointsContext

resolveGeneEndpointsContext(
  traversalContext: GeneTraversalContext,
): GeneEndpointsContext | undefined

Resolves concrete endpoint nodes for a traversal context.

Parameters:

Returns: Endpoint context or undefined.

resolveInterfaceNodeByOrdinal

resolveInterfaceNodeByOrdinal(
  context: OffspringMaterializationContext,
  nodeType: string,
  interfaceOrdinal: number,
): default | undefined

Resolves one offspring IO node by its stable interface ordinal.

Parameters:

Returns: Matching offspring IO node, when present.

resolveNodeByGeneId

resolveNodeByGeneId(
  context: OffspringMaterializationContext,
  geneId: number,
): default | undefined

Resolves one offspring node by its stable gene id.

Parameters:

Returns: Matching offspring node, when present.

resolveNodeTypeRank

resolveNodeTypeRank(
  node: default,
): number

Resolves a stable materialization rank for one node type.

Parameters:

Returns: Type rank used for rebuilt offspring ordering.

architecture/network/genetic/network.genetic.utils.types.ts

Shared constants and tiny contracts for the genetic crossover helpers.

This file keeps the "small but important" numbers used across the crossover implementation named and documented.

Most of these are not user-facing tuning knobs. They are intended to:

DEFAULT_REENABLE_PROBABILITY

Default probability for re-enabling disabled genes during crossover.

FIRST_INDEX

First element index used when reading newly created connections.

NO_GATER_INDEX

Sentinel index representing that no gater node is assigned.

PARENT_COMPATIBILITY_ERROR_MESSAGE

Shared compatibility error message for crossover parent validation.

RANDOM_BINARY_SELECTION_THRESHOLD

Canonical threshold used for random binary parent/gene choice.

RandomGenerator

RandomGenerator(): number

Shared random generator signature for genetic operators.

architecture/network/genetic/network.genetic.errors.ts

Raised when crossover is requested for parents with incompatible IO dimensions.

Crossover assumes both parents expose the same input/output interface. If they do not, there is no unambiguous way to align node slots and produce one runnable offspring network.

NetworkGeneticParentCompatibilityError

Raised when crossover is requested for parents with incompatible IO dimensions.

Crossover assumes both parents expose the same input/output interface. If they do not, there is no unambiguous way to align node slots and produce one runnable offspring network.

Generated from source JSDoc • GitHub