neat/compat/core
Core contracts for compatibility-distance mechanics.
This chapter explains how the root compatibility helper turns two genomes into one stable distance signal without dragging the full controller surface into every comparison. The boundary stays deliberately small: these types describe only the data needed to align genes, classify mismatches, and fold the final score.
Read this core layer when you want the "how" behind the root chapter: how generation-scoped caches stay valid, how sorted innovation lists are derived, how a linear merge walk decides whether genes are matching, disjoint, or excess, and how those counts become the distance used by speciation and diversity summaries.
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; shapes[Minimal genome and connection contracts]:::base --> cache[Generation and per-genome caches]:::base cache --> lists[Sorted innovation-weight lists]:::accent lists --> metrics[Matching, disjoint, excess, weight metrics]:::base metrics --> distance[Final compatibility distance]:::base
Practical reading order:
- Start with
GenomeLikeandNeatLikeForCompatto see which state the core layer actually depends on. - Read
ComparisonMetricsas the bridge between raw list comparison and the final NEAT distance formula. - Continue into
compat.core.tsfor cache setup, list comparison, and the distance fold itself.
neat/compat/core/compat.types.ts
ComparisonMetrics
Aggregated comparison metrics for compatibility calculations.
compareInnovationLists() produces this structure so the final distance fold
can stay separate from the merge walk that discovered the evidence. That
split keeps the algorithm easier to explain: one pass classifies the gene
relationship, a later pass applies the NEAT weighting policy.
ConnectionLike
Shape of a connection entry used during compatibility checks.
The mechanics layer only needs endpoint indices, an optional innovation id, and the connection weight. Keeping this shape small makes the comparison code easier to reuse in tests and controller helpers without importing the full network or genome implementation.
GenomeLike
Minimal genome shape used for compatibility distance calculations.
The _compatCache stores sorted [innovation, weight] pairs so repeated
comparisons within a generation can reuse the same derived list instead of
repeatedly normalizing the raw connection array.
NeatLikeForCompat
Minimal NEAT context required by compatibility helpers.
This keeps the boundary tightly focused on generation-scoped caches,
compatibility coefficients, and the fallback innovation resolver instead of
coupling the mechanics layer to the full Neat surface.
neat/compat/core/compat.core.ts
Compatibility-distance mechanics used by NEAT speciation.
This file holds the reusable inner loop beneath the controller-facing compatibility chapter: generation cache setup, innovation-list caching, linear list comparison, and the final distance calculation.
The root compatibility chapter answers what the distance means. This core chapter answers how that distance is assembled efficiently and deterministically. The helpers are deliberately small and ordered to match the real execution path: stabilize caches, normalize genomes, compare aligned innovations, then fold the discovered evidence into the NEAT distance formula.
buildPairKey
buildPairKey(
firstGenome: GenomeLike,
secondGenome: GenomeLike,
): string
Build a stable cache key for a genome pair.
Compatibility distance is symmetric, so the cache key must be symmetric too.
Ordering the genome ids ensures the pair (A, B) lands in the same cache
slot as (B, A) instead of duplicating work or storing conflicting entries.
Parameters:
firstGenome- - First genome in the pair.secondGenome- - Second genome in the pair.
Returns: Stable cache key in the form minId|maxId.
compareInnovationLists
compareInnovationLists(
firstList: [number, number][],
secondList: [number, number][],
): ComparisonMetrics
Compare two sorted innovation lists and derive compatibility metrics.
This is the heart of the compatibility read. Because both lists are sorted, the helper can walk them once like a merge step: matching innovations count toward aligned genes, gaps inside the shared innovation range become disjoint genes, and the remaining tail genes become excess. Weight differences are only measured for matching genes because that is the only case where the two genomes clearly refer to the same structural gene.
Parameters:
firstList- - Sorted innovation list for the first genome.secondList- - Sorted innovation list for the second genome.
Returns: Aggregated comparison metrics for distance computation.
computeCompatibilityDistance
computeCompatibilityDistance(
neatContext: NeatLikeForCompat,
metrics: ComparisonMetrics,
): number
Compute the compatibility distance from precomputed metrics.
This fold turns the raw comparison evidence into the familiar NEAT distance: excess structure penalty, disjoint structure penalty, and average matching weight drift. Structural counts are normalized by the larger genome size so larger topologies do not inflate distance merely because they contain more possible genes.
Parameters:
neatContext- - NEAT context providing compatibility coefficients.metrics- - Aggregated comparison metrics.
Returns: Final compatibility distance for the genome pair.
Example:
const distance = computeCompatibilityDistance(neat, {
firstGenomeSize: 12,
secondGenomeSize: 10,
matchingCount: 8,
disjointCount: 1,
excessCount: 2,
weightDifferenceSum: 0.9,
});
ensureGenerationCache
ensureGenerationCache(
neatContext: NeatLikeForCompat,
): void
Ensure generation-scoped compatibility caches exist.
The compatibility layer keeps pairwise distance results only for the current generation because the population can mutate between generations. Once that happens, earlier distances are no longer trustworthy. This helper provides the safety boundary that drops stale cache state before later helpers assume a cache map exists.
Parameters:
neatContext- - Current NEAT context holding generation and caches.
Returns: Nothing. The helper resets caches when the generation changes.
getDistanceCacheMap
getDistanceCacheMap(
neatContext: NeatLikeForCompat,
): Map<string, number>
Retrieve the generation-scoped cache map for pairwise distances.
This helper exists mainly to make the orchestration path read cleanly after
ensureGenerationCache() has established the invariant that the map exists.
It keeps the later flow focused on comparison logic rather than repeated null
checks or map initialization details.
Parameters:
neatContext- - Current NEAT context with the cache map.
Returns: Map storing cached distances for genome pairs this generation.
getSortedInnovationCache
getSortedInnovationCache(
neatContext: NeatLikeForCompat,
genome: GenomeLike,
): [number, number][]
Retrieve or build a sorted innovation list for a genome.
Raw connection arrays are not ideal for repeated pairwise comparison because
they may arrive unsorted and may mix explicit innovations with fallback-only
connections. This helper normalizes that surface into sorted
[innovationNumber, weight] pairs once, caches the result on the genome, and
returns the stable view that the merge comparison depends on.
Parameters:
neatContext- - NEAT context used for fallback innovation numbers.genome- - Genome to derive a sorted innovation list for.
Returns: Array of [innovationNumber, weight] sorted by innovation number.
Example:
const innovationPairs = getSortedInnovationCache(neat, genome);
// [[3, 0.12], [8, -0.7], [11, 0.44]]
resolveMaxInnovation
resolveMaxInnovation(
list: [number, number][],
): number
Resolve the highest innovation id from a sorted list.
The merge comparison uses this as the boundary between disjoint and excess genes. Once one genome's innovations extend past the other's maximum seen id, the remaining unmatched genes are no longer in-range mismatches; they are the structural tail that NEAT treats as excess.
Parameters:
list- - Sorted innovation list for a genome.
Returns: Highest innovation id or 0 when the list is empty.