All posts

Case study

Make loops, not flows

Generating playable levels was the easy part for a studio. Getting designers to accept levels generated by a program, however, is not.

bender-the-blogger ·13 min read ·Level-design series · part 1

Code on GitHubsynnada-ai/recurse-sdk

A mobile gaming studio had built an agentic workflow for their hex-grid puzzle game written in Python/Flask, and they were thrilled that the workflow was producing playable levels and even whole ‘scenes’ of levels. However, when we looked at the generated levels, they played flat – i.e. engagement flatlined at about 50% of player’s total engagement with this type of game. We rebuilt the problem and tackled it as an engine for designing good playable levels that would be shippable. After a few iterations, we were able to get the levels to 90% first-attempt completion.

fig 01engagement · summary

The deployment, in four numbers

50/50levels delivered
90%first-attempt success
34design tools
5rule-mining rounds

How it started

A Head of Data & AI from a studio that makes a mobile hit with hex-grid puzzle levels came to ask for our help with an agentic workflow they had set up in their studio to generate levels. I was shown four or so levels which looked seriously well-crafted and mostly worked to play through. They had been generated by an LLM-powered pipeline that, after specifying a few details, proceeded to generate a board, distribute pieces, add mechanics, validate the generated content, and finally dump it out in the game’s file format.

The levels were fun to look at, as levels for this game look, but not shippable. All the levels played flatly. The team behind the generation of these levels for this very popular game on mobile hand-crafts levels for this game for exactly the same reasons that hand-crafted levels for this game are shippable: they contain resistance to easy plays that the automated generation of these levels for this game here failed to incorporate.

The game, in one paragraph

The gameplay for this puzzle is very simple: pieces are placed in chains of hex cells, and when a piece is tapped it will slide in the direction it is facing out of the chain of cells it is in. A piece will escape from a chain of cells when all the cells on the line of the chain that the piece is on out to the edge of the board are clear of other pieces. There are many mechanics laid on top of this basic mechanism, such as ice that melts as other pieces escape from the same chain of cells as it, keys that are buried in locked boxes that must be opened with other pieces (also buried in locked boxes), chained pairs of pieces, pieces tied with ropes that must be cut with scissors, timed explosive pieces, etc. A good level is a knot – it looks like a knot, it is solvable, and it is of reasonable size.

Why a workflow can't design a level

When we looked at the numbers generated by the levels designed by the workflow compared to the vast repository of levels hand crafted by the studio for actual game play, the flatness was quantified to three numbers.

fig 02deconstruction · medians per set

Depth of workflow levels vs the shipped corpus

Shipped levels (median) Workflow levels (median)
Median per set (i.e. the different types of deconstruction within a set of levels). The workflow produced levels with a third of the depth of the average shipped level’s escape waves, a quarter of the average number of knots, and pieces that were a third the length of those found in the studio’s hand-crafted levels. In fact, of the 18 levels produced by the workflow, none of them were full boards, none of them were unique boards, and on average, each level produced by the workflow had only one knot in them as opposed to the 4 average for the shipped levels.
Data table
MetricShippedWorkflow
Escape waves20.56
Knots (forced convergences)41
Longest piece155

What flat looks like

The 6 images of the workflow for a campaign through 18 sample levels of escape depth show short pieces and long weaving pieces which are the depth carriers in the game. In addition to this the images also reveal a very serious bug in the ‘generate a valid board for a given number of players’ stage of the pipeline. All 18 levels were created using only two distinct boards. The pieces on each board were then shuffled to create the various levels.

fig 03workflow sample · delivered files

Six of eighteen workflow samples

fig 04sample vs final campaign

Two verdicts, same gates

The workflow's sample

0/18

2 distinct boards across 18 levels

Median escape depth 6 waves; corpus median 20.5

Features present but not load-bearing

The agent, by the final campaign

50/50

12 board families in one run, zero adjacent repeats

Gate medians 26 / 33 / 43 by difficulty tier

Every feature verified load-bearing before save

Notice that every generation workflow is a fixed sequence of generation steps. Irrespective of the number of LLM calls in the generation workflow, a workflow is a fixed sequence of steps. As such, a fixed sequence of generation steps is procedural generation. Each step in the workflow was designed to solve a local problem. The board generator generates a board. The placer placer places pegs on the generated board. The validator validates the solvability of the generated and placed pegs on the board. This means that every one of the quality features that you might like in a level (e.g. deep escape order, interesting knots that force converging solutions, peak of tension late in the level) are global, emergent properties of the entire arrangement of steps in the workflow.

Design is not a line of events. Design is a loop: design, play, find out what doesn’t work well, change that, play again. Design needs to be judged in the middle of that process, not after the whole thing has been generated. To design something is to have tools in the hands of something with taste and following a set of standards. This is an agent.

So we took the project: deconstruct first, prompt later

(The order in which I developed these steps is shown in Figure 05, below. Initially, there was no “Agent”. And, note that the step labeled “Tools + gates” was actually developed in weeks 3–5 of the course!)

fig 05build order · weeks 1–3

The sequence that ended up mattering

Corpus analysisevery shipped level, measured
Simulatorfull rules, exact escape order
Dimensionswaves, knots, traps, mobility
Tools + gates34 tools, hard validators
Agentdesigns, playtests, revises

Solvability by construction was a two part approach to building these boards, the first part being that every board that was shipped was analyzed through corpus analysis to measure up against every other shipped board, and then the board was run through a full Simulator of the rules of the game in question to test for escapeability in terms of exact escape order. The second part was that all of the dimensions of the game were laid out in a clear and coherent order. In short, all of the Waves and all of the Knots and all of the Traps for a game were laid out in a clear dimensional space prior to beginning to build the boards.

Simulator Test All Shipped Levels - 'solvability' as a sequence of events for each level in the corpus for all shipped levels (through histogram/normal distribution etc.). Means in simulator.

When people talk about deterministic tools, they usually mean that the tool itself is somehow determinate — i.e., guarantees something — and then the workflow somehow fixes up the rest to produce quality. But that’s the problem with the workflow in the first place: it attempts to fix up the sequence of steps to produce quality, whereas what you really want is for the agent to use whatever tools are at its disposal, to produce quality in the first place. The sequence is not determinate.

This is very uncomfortable. We compare our finished valid levels against the studio’s shipped levels. Only 42% of the studio’s levels are valid when run through the same validity gates we use for our hand-crafted levels. (As noted above, hand-crafted levels frequently break their own rules, but are ‘forgivable’ in play by the designer who made them).

First contact: three of four rejected

Our first test cases for the levels were the designers and other team members at the studio. They played through a number of the levels to test them out for us, and we got some interesting results. A lot of the levels got into a deadlock where pieces faced off against each other in a standoff. Occasionally a level was impossible to playtest for. After running seventeen of our levels through this process, four of them, or 24%, had passed.

These rules became data to analyze. Every comment of the reviewers resulted in a new rule for the judge script (e.g. “This unlocks in one move — it should be hidden like a key” becomes a rule for features that trigger other features: they have to be in escape-wave 2 or deeper).

  • “This unlocks in one move — it should be hidden like a key” → a burial rule: trigger features must sit at escape-wave 2 or deeper.
  • “Too many pieces free at once” → a wave-width cap per difficulty.
  • “The counter ignores the pieces in front” → A budget floor for every explosive’s counter that is determined by the measured escape order.
  • “Feels Normal, tagged Hard” → A knot and trap floor for each tier.

Then, to our surprise, we just compiled the reviewer. The rules were added to the judge script and carefully calibrated to exactly reproduce the human reviewer’s decisions on all 17 levels that had been reviewed so far. Then, in subsequent generations, every single level was judged by this script, before ever being reviewed by a human, and rejected by the save tool if it failed to meet the reviewer’s criteria.

The mistakes were ours too

The workflow for failure in generating these simple levels was clear to retrospect on. It existed in data for the project, and can thus be recounted here too.

fig 06first delivery · postmortem

The shipped deadlock

As an example, we show the “locked” or “deadlocked” scenario that the team shipped in their first delivery (see Figure 06). In this case, two pieces are set up to face each other head-on. Each piece has a tap that slides the head of the piece to be in the way of the other piece’s head. However, neither piece can ever be tapped in order to leave. The levels solver in the construction tool proved that this level was solvable in abstract escape order, but the levels simulator did not at the time replay all of the taps to arrive at a solution for a level that had been solved in the construction tool.
fig 07postmortem · four fixes

Our own failure modes, and what each became

1 · We shipped deadlocks

Two different failures were discovered in the first delivery of levels that could not be completed by players: head-to-head facing pairs, as well as pieces with heads that cross each other’s tails. These failures were not ones that required special care, but rather were failures of construction that were solved in the end by construction: by placing pieces in reverse escape order so that there was always a clear path to escape, and by replaying the full tap sequence of a level through the simulator before allowing it to be saved by the author.

2 · Features as decoration

These elements, including the cuttable rope, key on open padlock, and chained pairs, were included in levels as what seemed to be ‘extra’ features. Although they were cuttable, they added no value to the completion of a level. In fact, in many instances, they only served to confuse players. A load-bearing test per feature would have demonstrated that these elements added nothing to the solve of a level. If removal of a feature resulted in no change to the solution then it was, by definition, decoration. It should have been rejected by review.

3 · One fingerprint isn't originality

Automating the search for duplicate puzzles led to finger printing puzzles (i.e. escape structures) and we found one nice board configuration for puzzles which the generator then created hundreds of times more. All of these created levels were technically distinct but looked all the same. I had misunderstood originality. It is two-axis: shape (silhouette) of the puzzle and the actual puzzle itself.

4 · Trusting the success rate

The success rate of runs (acceptance rate of the pavement) does not correlate with quality (stiffness of the completed level). It is easy to write a run that keeps succeeding while quality is slowly but surely drifting down. In other words: success rate only measures the quality of the enforcement layer, not the quality of the levels that are accepted. Gate depth, drift, and variety need to be measured for each run against the corpus distribution.

The bar moves every time you clear it

As more accepted sets of levels were added to the corpus of accepted levels, the standard for future levels became higher and higher. Levels with multiple floors could be created and were considered “hard floors” for future levels. The bands for opening moves were made tighter and tighter and levels with pressure features were marked as “mandatory” for the top tier of levels. The rule-mining that occurred in 5 rounds of feedback is listed out below:

fig 08rule-mining · rounds 1–5

Five rounds, each ending as code

RoundWhat the feedback taughtBecame
1Full boards, originality vs corpus, hole placementvalidity gates
2Difficulty bands, pressure features, trigger burialtier floors
3Feature ordering, required mechanics, measured flagshipsreview gate
4Opening bands, effective depth, explosive marginsper-tier policy
5Gate-aware pressure, separation, pacingfield recipes

Three eras, side by side

fig 09three eras · delivered files

First delivery → encoded rules → final campaign

And the figures climbed with it:

fig 10review records · all stages

Share of levels passing review

Measuring this over the entire workflow for both the studio’s shipped corpus and the newly written workflow levels (note: these have been reviewed with the same gates as the agent levels, for honesty in comparison), shows that while the first review round of the agent actually performed worse than the human reviews for the corpus, by the time it had been rule-mined and then run through a 50-level campaign, it had shot past the high water mark of both. Note that the share figures for the first-review round were calculated using the human review round figures for the corpus levels, rather than the scores of the automated gates subsequently used for all stages.
Data table
StagePassingOfShare
Studio's shipped corpus256042%
The workflow levels0180%
Agent — first review round41724%
Agent — post rule-mining1212100%
Agent — final 50-level campaign5050100%

What the levels look like

Above, three examples of levels from the final campaign – one for each of the three difficulty levels (Regular, Hard, and Very Hard) – shown as they were delivered to us (i.e., rendered for web display here). A piece’s color indicates its type. An uppercase letter atop a piece announces a special feature (e.g., I = ice, C = chain). Dark-colored cells represent holes.

fig 11final campaign · levels 203 / 204 / 249

One level per difficulty tier

Twelve board families, one campaign

In terms of variety, the studio locked down scheduling of the fifty briefs to lock down 12 board families (above, one silhouette from each family) and 9 different mechanics. So although there are 50 levels, for example, neighboring levels will not have the same board or be of the same type.

fig 12final campaign · 12 families

Twelve board families, one silhouette each

The final campaign

Instead, for the final campaign all 50 briefs were processed in a single unattended run. Numbered 201-250, the levels ran on a scheduled basis and employed 9 different mechanics and 12 different board families, so that no two adjacent levels were created with the same families. Of the 50 levels five were defined as Hard and five as Very Hard.

Ninety-seven minutes later the fifty levels were done. 45 of the 50 briefs worked on the first try, every subsequent try then arrived at the solution within 4 attempts in every case. The increase in attempts however focused on the right areas:

fig 13run ledger · 50 briefs

Attempts needed per level, by difficulty tier

Final campaign, 50 briefs. Harder briefs bought more revision loops, not lower standards.
Data table
TierLevelsAvg attempts
Regular401.20
Hard52.00
Very hard52.20

You shipped it. Terms and conditions for the future work of the studio were agreed and represent the healthiest sign that all of this is working at all: one revision pass through all files and thereafter every generation of output of the agent is billed at the same price as output from a designer.

The accidental experiment

Most useful accidental finding for the year:

I experimented with a very modular version of the agent code: 34 fine-grained tools are now 7 clean tools, the floors for the policy are now stored in data not code, and the architecture is better. I ran two 50-level test sets with the same model and same simulator and same enforcement settings as before. Here are the quality results:

fig 14rewrite experiment · 3 runs

Flagship gate score by difficulty tier

Baseline agent Rewrite, run A Rewrite, run B
Median per set. This score expresses how many entities a level’s headline mechanism actually controls. For the three tiers of hardest levels, the score remained about the same, even declining slightly for the hardest, because the floors for these were enforced as code. However for the regular level of greatest volume, the median score for the rewrite’s 3 runs fell dramatically from 26 for the baseline to 7 and 4 for the two runs of the rewritten code. The capability to build such deep levels was clearly present in the tools of the rewritten code; just nothing caused the model to use them.
Data table
TierBaselineRewrite ARewrite B
Regular2674
Hard331930
Very hard432338

The diff is quite simple here: I had retained one of the two layers of the existing agent and removed the other one.

fig 15rewrite diff · two layers

What the rewrite kept and dropped

01

What the tools refuse

Per-tier floors as data (piece bands, knots, traps, margins)

The review gate: save refuses a failing level

Solvability by construction

Kept — enforcement
02

What the prompt aims for

The 128-line prompt shrank to 10; field recipes gone

Flagship targets: declare a gate depth, hold it through review

Theme discipline, palette bands, variety rotation

Lost — instruction

One thing in particular stands out from this test case: The simple enforcement of floors that were already coded to work worked just fine, while the introduction of the same levels of excellence in terms of instruction to the AI resulted in completely different (and much worse) levels.

We found two more smaller issues in this set of tests, of the class-of-bug type, where instead of failing tests, the AI would silently produce lower quality work.

  • Sampling parameters are per-model facts. This is a simple fact about the newest model family that does not get to decline to perform when an API temperature is set (in contrast to the old models, which do decline to perform when an API temperature is set at all and must have it pinned to 0). The basline handled the sampling parameters for each model as a per-model fact; the rewrite has them all as a single fact, hardcoded to the worst value for the hardest brief. Near-duplicate boards on adjacent levels.
  • Timeouts are quality parameters too. We noticed in the test cases for the very large models (like DoomSketch big) that actually implement a timeout parameter for the model that can be set at the API level for the newer models (not at framework level for older models) that atimeout had to be set for the largest models (to allow them to run to completion on long design steps), and that had to be set (for the largest models) at the framework level (in the Baseline, this had been raised from the default of 300 seconds or so); in the rewritten prompt, this had been lowered back to the default (so that very long design steps failed to complete in the middle of the design – ie. did not complete at all).
By contrast, the investigation itself — attempted as a single day’s work — took all that time because none of the test sets could be attributed to the code that generated them. (This problem has since been solved by having every harness run write a commit hash, prompt hash, and model id to a file for every attempt.)

What we'd tell you to steal

  1. Build the simulator before the agent. Just as the provably correct elevator was developed first and then turned into an agent, the kind of guarantees you want for your levels can only be created by a deterministic system that your model controls, i.e. by simulator code, not model code.
  2. Measure against the corpus, not against taste. The shipped levels define the distribution; the agent's job is to land on it or explain why not.
  3. Compile the reviewer. Once you have gotten the feedback from the human — turn it into a rule! That rule then runs on every subsequent level for free.
  4. Let the bar rise. It is easy to set up an acceptance test that fails in two rounds of development. But then the test is stale. The only good test is one that fails from time to time because it isbeing used. Every review that finds a new failure mode causes the gates to rise and success rate to climb.
  5. Separate, but keep both enforcement and instruction. Write floors in code. Set high ambitions in prompt. The rewrite experiment showed that, as opposed to floors, ambitions are easy to refactor. So, keep only the layer that is easy to refactor.

All figures were collected from run ledgers and review records, including the number of human reviewers who verified levels before they were passed to automated judges for later stages of review.

Give your agent its next job.

Connect Recurse to your agent, and then ask it to do something for you there.