All posts

Case study

No rulebook, no tells

We created a level-design agent without a rulebook and asked the designer of the studio in question to spot the additions of the agent.

bender-the-blogger ·12 min read ·Level-design series · part 2

Code on GitHubsynnada-ai/recurse-sdk

No rules. No rule book. Ever. I had reverse-engineered rules from 100 shipped levels and had constructed an agent to generate levels based on my best guesses. The studio’s sole level designer had sorted 20 generated levels into those he thought had been written by a machine and those that he thought had been written by a human. He had done well, identifying 14 of the 20 generated levels, but described the levels that he had correctly identified as being nearly indistinguishable from his human-written work. Five days later, with three new physics models under my belt, he’s no better than guessing at identifying the generated levels (which were now shipping at 90% acceptance).

A studio with a live puzzle game on the web agreed to let us use 100 production levels as a basis for a challenge: could we get an AI agent to write levels for the campaign that the AI wrote, and would a designer be able to tell the difference? The only evaluation that we set up before writing a line of code was a blind test: we would write 20 levels and the studio would pick from those plus 20 real levels, without knowing which were which. The designer’s picks had to be no better than random.

This set up is actually 90% of the work to create an acceptable test. Because your acceptance test is actually a human, trying to catch your AI, you will make all kinds of decisions along the way that bend to the one question: What can he see that you can’t measure yet?

No Rulebook, No Tells

But we didn’t get a rulebook. No design document, no style guide, no list of shippable level criteria. The studio designer had 3+ years of ‘taste’ in their level design, but all we got was a folder of levels and a deadline. Every rule listed in this article (e.g. levels oriented this way, devices have this many tokens per type, levels in this corpus band, etc.) was reverse engineered from the levels themselves or else we lost a round to not implementing it.

This shot was taken almost blind, just relying on the simplest 3D models for the player agents and on several intuitions about what would be hard to recognize, and hit way closer than we expected.

Level Design in Practice

The puzzles are of type drag-and-collect. In a drag-and-collect game colored polyomino containers are placed in a grid on which also lie same-colored tokens, scattered around. In drag-and-collect games a container is moved across the grid by dragging it with the mouse and all tokens of the same color as the dragged container which are reached by it are filled (boarded) by it. The container which contains tokens is removed from the grid after it is full. The studio in addition to these basic elements of the game also uses spawners (which can be placed on the border of the grid) which stream tokens of a certain color one after another in order, hidden second layers of containers (which double the capacity of a container for a certain token type after this container has been filled for the first time for that token type), locks, obstacles and axis constraints. A good level is shaped in a certain way rather than simply being hard.

fig 01anatomy

Anatomy of a good level

SPAWNER · queue 5 1 2 3
4
  • 1Fast opening. Two blue tokens sit one drag from the blue container — the player scores in the first seconds. The studio never opens cold.
  • 2One far route. The red container's second token is across the board. One or two long collection routes carry the difficulty; the rest stay near.
  • 3Budgeted devices. One hidden layer (the "4" badge doubles a 2-cell container) and one spawner. The studio has set a limit of three different types of devices per level.
  • ·Portrait, balanced, imperfect. These levels are taller than they are wide and asymmetric in weight distribution. They are never mirror-symmetrical. While clean and neat, these levels are distinctly human.
fig. 1 — a certified generated level for a late-campaign slot. Every one of these properties was, at some point in this project, a detection tell we lacked.

Deconstructing the studio's hand

First, we reverse-engineered the already generated levels in the 100-level corpus (eight different schemata realized in 100 levels), and searched for an exact per-color conservation invariant holding in the levels. After that, we reversed-engineered the game’s dynamics, that is, how levels are played, by frame-stepping through the game’s gameplay footage, figuring out the queue order, lock semantics, and movement rules, by inspecting the pixels on the screen, as opposed to the studio people telling us how to realize the levels.

The key figure here is the positional corpus window which compares a generated level candidate for a campaign slot N against a band of levels for the same slot N. The studio’s hand changes across the campaign. The levels in the game grow in size, the headroom between the playing field and the top and bottom of the screen decreases and the number of devices in the game increases. Thus, a level for a campaign slot N has to be within a band for each of the ~20 features for a level of that slot N.

fig 02corpus window

One candidate level vs the corpus window for its slot

corpus band (slots ±15, p10–p95) inside band outside — flagged
board cells
containers
board tokens
queued tokens
adjacency rate
headroom
aspect ratio
fig. 2 — The above picture illustrates one major problem. Out of ~20 features for a potential level for round 1, seven are shown here for a level that had great promise but failed in one crucial area. Six of the features for this level were inside the band for its slot, but the feature for the aspect ratio was far outside the band for the aspect ratio feature of its slot. This one failed feature was enough to keep it out of round 1.

First, the baseline is perfect on paper – 20 generated levels, all solvable, all valid within their respective corpus windows, and five golden evals all green. I shipped the first set of the “blind pack” set of levels with some confidence.

fig 03pipeline

The pipeline

100-level corpus gameplay footage rules model LLM agent certificate critique gate action tools blind test mined composes via zero FAILs solvable ship designer feedback re-enters the rules
fig. 3 — nothing ships unless the solver proves it solvable and the corpus-calibrated critic finds zero failures. The dashed edge is the loop that mattered most.

There was nothing wrong with the basic approach of making 20 levels. The levels looked great, the solvability was guaranteed, the corpus windows all fit. All 5 gold test cases passed for all 20 levels. We shipped the first blind pack of our Level Generation Studio with great confidence.

Round 1: caught in twenty minutes, and praised anyway

During round 1 our designer even managed to score 14/20 (p = 0.013) while only superficially looking at the generated compositions. And indeed, one single distributional property by far dominated the designer’s scoring: the board orientation. The reason for this, of course, is that the studio never had managed to ship a landscape level before, but we had managed to ship 7 out of 20 test levels.

fig 04round 1

The round-1 failure, side by side

7×5 — landscape. Instantly flagged.
5×7 — same content, portrait. Invisible.
Why it mattered The phones we test on are held up to watch video. The designers at the studio work on thumb-sized screens that are tall. All of the studio’s 94 good levels are taller than they are wide. A program that doesn’t know this one simple fact about human game designers immediately announces its presence.
fig. 4 — the entire round-1 loss in one property. The fix was one hard rule and one new admitted feature (aspect ratio, fig. 2’s red dot).
fig 05orientation

Board orientation — the round-1 fingerprint

studio corpus (n=94) generated set (n=20)
90%
55%
10%
10%
0%
35%
portraitsquarelandscape
Fig. 05 — A fingerprint, nonetheless. Although per-level validity alone did not establish this characteristic of the studio corpus (n=94) / generated set (n=20) of 5×7 compositions (n=94), here it is.

Another important point from Round 1 that initially went unobserved is illustrated in Figure 8 below. How did the level designer feel after the studio designer had sorted the 94 levels by their quality? Clearly, the designer was very impressed with the results. In particular, he felt that, aside from the obvious errors in the studio levels, the generator levels looked as if they were designed by a human.

“If it weren’t for the obvious mistakes, it is nearly impossible to distinguish this from a level designed by a human.” — the studio’s level designer for round 1.

— the studio’s level designer, round 1

This was also surprisingly good at replicating human composition (see Fig. 7). The onlythings it got wrong were obvious (i.e. mistakes that would be immediately obvious to a human designer). Some of these mistakes are enumerable (e.g. the studio used a ‘landscape’ board a number of times; it used a ‘Device Rate’ that was way too high to ever be shipped). Others are not (yet). As mentioned, the designer felt that the ‘obvious mistakes’ in the compositions were the easiest things to spot, and so the entire project was redirected towards closing off the list of tells that the studio had come up with for the ‘obvious mistakes’ rather than starting from scratch to teach the agent to have taste.

Expert feedback must be verified. Of the mistakes the designer said the system wasn’t able to spot that he listed out, one of them was actual (orientation), one of them was false that we were able to very quickly prove false (the designer had incorrectly stated a movement-constraint rule for the system and we were able to very quickly disprove 3 ways with designer-level content), and one of the ‘flaws’ that the designer said the system wasn’t able to spot for a level, actually pointed out a flaw in one of the studio’s own levels.

Round 2: Guessing at the Physics & Losing Twice

The follow-up round involved us bringing our levels into the live game and having the designer play through them to identify the generated levels. He correctly identified all 6 machine levels in the set of 12 created (6 human + 6 generated). Interestingly, the reasons for the designer’s identification of the generated levels as not being human created was because none of the machine generated levels could actually be completed. His reasons for this were found to stem from an error in our rules model for how collection works, an error which would later be found and corrected in two subsequent physics models.

fig 06physics models

Three physics models, two funerals

v1 — lift & drop v2 — slide + collide v3 — slide, cover-only place anywhere legal; collects adjacent tokens pieces block the path; still collects adjacent must slide UNDER a token; adjacency only at spawners
fig. 6 — each model was killed by evidence: v1 by an interview, v2 by the in-engine play test. The teal outline is the collection rule, shrinking toward the truth.

I will present to you what the level might look like and how it certifies under each of the physics models and then dies to under the evidence found in each respective model’s flaws.

fig 07the dead token

A level that certifies under v2 and is unwinnable under v3

The dead token Note to figures: The “red container” in the figures needs to have a token marked with ×. Under v2 physics, the way to collect the token by parking next to the pocket is shown on the left in fig. 6. For the same layout under the real rule of the engine, the container has to slide underneath the token which is not possible because of the obstacle pocket in front of the token. So this level (and six more of the six levels in the play-test round) is invalid. This figure shows unsolvability, which cannot be argued with. After this round “solvable” means solvable under the engine’s physics, not under the physics which the authors of the level had in mind. So they were very wrong indeed. Six of the six levels were solvable under v1 physics (fig. 6 left, see above). Later the solvable levels are in fig. 4. The solver had proven solvable all 100 corpus levels under v1 physics, i.e. a perfect score. That this was only possible because of wrong physics, i.e. of very generous physics for hard problems, was only discovered later. The score for the solver under true physics (v3 physics) was 20 points worse, i.e. 75 points. All these points were regained by improving the search function, i.e. in total 6 points were added in a later iteration. (All corpus levels were valid, only their solution status was tested).
fig. 07 — unsolvability is the one tell that cannot be argued with. This image after the round was defined to show “solvable” meaning solvable under the engine’s physics and not the solver’s.

All 100 corpus levels were solvable under v1 physics (a perfect score for the solver). However, in truth, all those levels were unsolvable under the strictest model and had to be hand-tuned to be solvable. Initially, the solver could solve only 75 of 94 usable corpus levels under v2 physics. But, with sufficient search, this was upgraded in turn to 81 of 94 levels.

fig 08solver coverage

Solver coverage of the studio corpus, by rules model

100%*
89%
80%
86%
v1lift & drop v2slide + adjacent v3cover-only v3 + upgradesengine-true
fig. 08 — * the perfect v1 score was an artifact of wrong physics. Truth cost 20 points and had to be bought back with better search.

Round 3: reaching chance

All twenty levels were re-generated for the new physics rules model. This can be witnessed in the journey of one single campaign slot in Fig. 09.

fig 09three generations

The same campaign slot, three generations

round 1
landscape; caught on sight
4
4
round 2
portrait — but a hidden layer in 13/20 levels; unsolvable in-engine
SPAWNER · 4
round 3
engine-certified, devices at corpus rate; passed clean
fig. 9 — three generations of one slot. Round 1 fails on a set statistic, round 2 on physics, round 3 passes review — each failure became a permanent check.

For this test the designer took a more refined approach, and flagged 4 of the 6 “real” levels, allowing 2 of them to be published in their normal state. The designer was also very quick to finger-print a single level as being “machine-generated” and even provided solid reasoning for the levels slight asymmetry. The detection rate for this test was a rather underwhelming p ≈ 0.12 (or in simple terms: he basically guessed correctly).

fig 10detection

Designer detection across rounds

chance
70%
100%
58%
round 1screenshots · p=0.013 round 2played in-engine round 3played · p≈0.12
fig. 10 — The round-2 spike here was very useful for us to understand what went wrong in the project. However, Round 3 levels are not significantly above the dashed line of chance here.

In our later review calls of the levels the designer of one of the machine-made levels of "Hard" difficulty stated that "It was a good level — it challenged me, its balance was right. Had it been submitted to me, I would have approved it."

"It was a good level — it challenged me, its balance was right. Had it been submitted to me, I would have approved it."

However, during that same call, knowledge of the “unwritten house rules” for critique were revealed, including the most important one: that colors in a hidden-layer container must never be used in other hidden-layer containers. This was an important rule for the studio as it prevented the “dead-end” state that had once caused them so much pain. 87 of 94 levels in the studio’s corpus comply with this rule (see fig. 11).

fig 11house rule

The dead-end the house rule prevents

4
Order-dependent death In addition to this failure, we found another: Two teal containers of the same color, one of them a layered container (marked with a badge of capacity 4), and the other a plain container (marked with a ×). If the plain container collects two teal tokens and clears first, than only two tokens remain for the layered container, which still needs four of them. This level is then dead and the player cannot even tell in advance that it will be. We call this failure order-dependent death. This is a failure that the studio had a hard time with in the past, when they were burned by a level that had hidden-state (a state that the designer had forgotten about). The studio’s solution for this was to implement a rule, which we call a house rule: a layered container’s colors belong to it alone. We measure the corpus of levels that the designer created for the studio. For 87 out of 94 levels this house rule holds.
fig. 11 — feedback at its most valuable: a constraint that exists nowhere in the data files, only in the studio’s institutional memory of a bug that hurt them.

Each failure found in reviewing levels further solidified the hard checks in our critic. The first blind test’s critical remarks became house style, even with the bands between the different types of statistics sometimes varying significantly. Each of these checks was also calibrated against the entire corpus before being added to the critic.

fig 12critique gate

Hard checks in the critique gate, cumulative

8
10
13
16
baseline after r1 after r2 after r3
These bands also form the basis for the critique gate’s checks in Fig. 12. In this case, however, all counts are approximate and the values for each round have been averaged before being shown for all twelve rounds of feedback and recorded in the final product.

The regression that taught us the most

Then a quality regression creeped in. Swapping in a newer base model meant that we’d certified a new set of levels. And this set of levels used zero optional devices. In other words, there were no hidden layers, no obstacles. Just twelve open and airy levels to play. This was in stark contrast to our own earlier sets, where we used a device in about 1 in 4 levels on average. (As an aside, the designer had previously pointed out that sets are more meaningful than levels.)

fig 13device rate

Hidden-layer usage rate, by set

studio rate
65%
25%
0%
early setoveruse calibrated setcorpus rate new-model settotal avoidance
fig. 13 — both extremes are fingerprints. The studio uses the device in about 1 in 4 levels; a set that always or never uses it gives itself away without a single bad level.

The most obvious hypothesis is that this new model is just afraid to be creative, but, after some wresting with this idea, I came up with an A/B test: take the exact same 12 briefs, run them through the previous model in the exact same codebase, but with the setting to “use devices at the studio’s rate”. What happens? The entire generation collapses to produce the barest minimum of scenes, every single one of them failing for the same reason.

fig 14model a/b

Save rate, same 12 slots, same code

9/12
4/12
6/12
new modelold gate previous modelold gate new modeldevices required
fig. 14 — the A/B that flipped the diagnosis: when both models fail the same way in the same environment, the environment is the cause.

So the real mechanism for the flops in both cases was a victim of the generator's own acceptance gate. We had assumed the solvability certificate (a solvability certificate is a semi-decision procedure — i.e. it may fail to confirm a solvable level but never claim an unsolvable level is solvable) was something that was exercised by the solver’s search parameters. But in practice it was simply a different view on the same problem, and the problem the generator was trying to solve (create an acceptable level) was the same set of views that the solver was trying to solve (solve the level). As such the generator had learned the same lessons as the solver. And the verifier’s blind spots had been quietly inherited by the generator. Even the designer of the new model had commented that the levels generated by it looked “flat” — ie they had the solver’s fingerprint on them, not the model’s.

By the time I even considered these potential fixes I had cheaply confirmed or rejected them. Making search width larger did nothing for 13/14 of the levels, and a short amount of trial and error setting the device requirement for the brief within a campaign slot, and verifying that the studio had introduced that device to the player in the relevant level, confirmed that in the cases where a model did indeed try to solve a level that required a device in a brief, saving such a level would drop to 50% (since the level is a “corpus” level that the gate cannot verify for solvability). And further, I found a self-inflicted bug in the case where a brief required a device for a campaign slot level: the brief would fail to be accepted by the agent in those cases (because there was no device introduced by the studio for that level yet).

To solve the problem, we invert the problem. Instead of generating levels and then solving them, the generator can produce proof of solvability for a level, and the verifier can replay the solution to verify that the level can be solved. That’s to say, instead of scattering tokens until a level is solvable, we search backwards from a solution to a level to populate the board with tokens that solve the level. Every level would then come with a solution witness for the level, which could be replayed for verification rather than re-searching for a solution, and the length of the solution witness could be a way to vary difficulty for the studio, which is the next thing they want.

outcomewhere it landed

Blind start, shipped agent

0rules handed over — every constraint inferred from the corpus
14/20caught in round one, on obvious tells rather than composition
p ≈ 0.12detection by round three — statistically, guessing
90%acceptance rate in production operation
The arc. In round one we failed to complete an enumerable set of tasks. This list of things to do rather than tastes to pursue was sufficient to bring to life an agent that can be deployed by the studio.

What transferred

  1. Put the ground truth outside the code. This blind test of our story using a human as an expert found all the issues with our internal test metrics that we had failed to find before. It also provided a clear answer to ‘done’.
  2. ** Rules are hypotheses.** Three physics models. Two of them were killed by evidence when put to the test. A validator who relies on unchallenged assumptions flatters himself to think he is validating.
  3. Distributions are the tell, not instances. Every detection round was won or lost on set-level statistics — e.g. the numbers of orientable cavities, amount of devices to be placed of various types — none of which can be derived from any individual level’s features.
  4. Verify the expert. Feedback is only encoded if it survives contact with the data (i.e. is true): one of the designer’s assertions was treated as gold, one was false, and one attributed a level to the wrong studio.
  5. Your acceptance gate shapes your generator. Any learned or searching producer will drift to what your verifier rewards. Therefore the bias in your output will look like a model problem until you A/B the environment.
  6. When verification is the bottleneck, generate certificates. All generated levels are solvable. The hardest check is then no longer a problematic search but a simple replay of a solution.

Give your agent its next job.

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