TO APPEAR · IEEE CONFERENCE ON GAMES (CoG) 2026

Procedural Content Metageneration
via Program Search and
Continual Abstraction Discovery

Programs that discover their own primitives

Matthew Siper  ·  Ahmed Khalifa  ·  Julian Togelius
Game Innovation Lab, New York University  ·  Institute of Digital Games, University of Malta

M. Siper, A. Khalifa, and J. Togelius, “Procedural Content Metageneration via Program Search and Continual Abstraction Discovery,” in Proc. IEEE Conference on Games (CoG), 2026, to appear.

Continual Abstraction Discovery in one run: a generator evolves, a recurring block of reachability logic is lifted into helpers.py, and later candidates call it instead of rewriting it.

Instead of generating a level,
we search level generators

Most procedural content generation searches the space of artifacts: you evolve a level, score it, and evolve it again. Metageneration searches the generator itself.

A program can be compiled, executed, inspected, edited by a designer, and reused outside the search system. Large language models are now good enough at writing and revising code to act as the mutation and crossover operators inside an evolutionary loop.

Searching over raw code fixes the vocabulary that variation can draw on. As programs grow, useful routines for reachability, entity normalization, repair, and structural construction get independently reinvented in candidate after candidate.

Continual Abstraction Discovery (CAD) is the response: during the run, extract those recurring routines from high-fitness programs into a run-specific helper module, validate them, and refactor the source to call them. The searchable vocabulary grows while the population is still being optimized.

The evolutionary loop

We use an evolutionary algorithm to search the space of code to find level generators for different games.

Chromosome
def generate(context_dict):
    """Receives grid dimensions and an initial level, returns a tile grid."""
    w, h = context_dict["width"], context_dict["height"]
    level = Grid(w, h, fill=TILES.EMPTY)
    # local functions, constants and layout routines are free to vary
    return level
1

LLM mutation and crossover

A parent is sampled from the archive with fitness-proportional probability. Mutation asks for a strong behavioural edit; crossover (probability 0.25) asks the model to combine compatible mechanisms from two parents into one executable program.

2

Written reflection

After every generation an LLM reflection call appends a structured entry to a per-run memory file: what was attempted, what happened, and the global lesson. That memory feeds later variation calls.

3

Continual Abstraction Discovery

From generation 8, and every five generations after, CAD extracts reusable helpers from the programs at or above the 75th percentile of fitness. A helper is kept only if it compiles and passes a smoke test, and the program is refactored to call it only if behaviour is unchanged on a fixed-seed test. Survivors go into a run-specific module that later variation calls can use.

FITNESS

F = ⅓ (V + Q + D)

V is the fraction of sampled levels whose quality equals 1.0, Q is mean level quality, D is mean pairwise diversity. All terms lie in [0, 1]. Candidates that crash or fail validation receive fitness zero.

Evolutionary lineage of the best Dangerous Dave generator, showing code introduced at each step and levels sampled from the corresponding generators.
Figure 1 — the lineage of one generator. Each callout summarizes the code introduced at that step in the best Dangerous Dave run, and each image is a level sampled from the generator at that point. New generators only need to be better than the starting seed to be accepted into the archive.

LLM generator mutations on different games

Best generator per game domain

The program on the right is executed one line at a time. The level on the left is its actual state at that moment — a loop stays highlighted while its body runs, and every tile appears on the pass that placed it. Each of these programs was run under a line tracer, so what you see is the real execution, not an illustration of one. Every level ends with a playability check under that game's own movement rules.

The effect of CAD and having an API

The main study crosses CAD with access to a fixed, hand-written expert API. That separates having a reusable vocabulary from discovering one.

without CAD
with CAD
Base
empty helper library
Raw program search
The LLM writes everything from scratch every time. Useful routines are reinvented independently across candidates.
Discovers its own vocabulary
CAD extracts primitives from high-fitness programs mid-run. Best programs also get shorter — roughly 370 → 296 lines.
Expert API
hand-designed helpers
A fixed expert vocabulary
Validated primitives for entity normalization, reachability, repair and structure — e.g. connect_floors_with_ladder, ensure_one_player.
Both — and it still helps
The largest single effect appears here, in Lode Runner: the CAD trajectory keeps improving while the no-CAD one plateaus.

CAD raises mean final best fitness in all eight domain and API comparisons.

Best-so-far fitness over 50 generations in Sokoban, Zelda, Dangerous Dave and Lode Runner. Solid lines are CAD, dashed lines are no CAD.
Figure 2 — best-so-far fitness over 50 generations. Solid lines show CAD, dashed lines show no CAD; shading is mean ± standard error and legends give the completed run count per cell. The plots use CL as the implementation label for CAD.

CAD-discovered primitives

Independent runs converge on a recurring core of validation, reachability and structural operations — and then trail off into a long tail of run-specific utilities shaped by the domain and the search history.

AbstractionCategoryTotal calls
In BoundsOther4,693
Entity Count NormalizationValidation3,848
Ensure One PlayerValidation2,611
Find Player PositionUtility2,370
Ladder PlacementStructural768
Flood FillOther569
Find TilesOther574
Grounded Empty CellsUtility465
Reachability BFSValidation344
Relocate Unreachable EntitiesValidation320
Connected ComponentsOther297
Deep Copy GridOther118

Table I. Semantically equivalent functions are grouped; Total Calls is the aggregate call count in the supplied audit. in_bounds and Entity Count Normalization each appear in 28 of the CAD runs; Ensure One Player and Grounded Empty Cells appear in 24; Find Player Position, Ladder Placement, Reachability BFS, Relocate Unreachable Entities and connected_components each appear in 20.

The interesting cases are the domain-specific ones. In Lode Runner the search abstracted a function that places enemies beside gold, making levels harder — and subsequent generators kept calling it. Something similar happened in Dangerous Dave, where CAD produced a function that arranges hazards around the solution path, which then shaped how later generators placed objects.

Figure 5 — two learned primitives applied to a level. Each pass shows the real before/after pair from the paper: guard_gold drops an enemy beside a gold piece in Lode Runner, and place_enemies_away_from_critical places one off the player → key → door critical path in Zelda. Both levels were decoded from the paper's figure, so the highlighted cell is the tile that actually changed — one tile, in each case.

M. Siper, A. Khalifa, and J. Togelius, “Procedural Content Metageneration via Program Search and Continual Abstraction Discovery,” in Proc. IEEE Conference on Games (CoG), 2026, to appear.