Loading

Mehmet Bulğak

I graduated from Yıldız Technical University’s Communication Design program and have worked as a full stack web developer for five years. In mobile games, I design levels that teach mechanics clearly and balance the difficulty flow. I evaluate design decisions using player feedback, bot tests, A/B comparisons and product measurement plans. In Aplomb, Mozaik Blast and Crumble Crew, I develop both the design and product sides of the games, from level design to Unity editor tools.

Mehmet Bulğak

Aplomb

Overview

In Aplomb, you balance a structure hanging from the ceiling. Placing a piece means deciding both where it belongs and when to hang it. If an arm tilts too far, its pieces slip back into the tray and you can try another approach.

I wanted to make a calm puzzle you could learn through short attempts. After a mistake, you can keep playing and rebuild the balance. New rules appear as you progress, all built on the same relationship between weight and distance.

My work on the project

  • Level design: I designed the game’s 84 levels, defining their learning goals, mechanic progression and pacing.
  • Tests and improvements: I built a solver, a level editor and bot tests. I revised 15 levels based on the results, then checked both individual levels and progression through the game.
  • Product decisions: I designed the boosters, rewards and shop, and added analytics events. I planned a bot A/B comparison of the placement preview and analysed its data with SQL and Excel.
  • Implementation: I developed the game rules and tools in C# and Unity, and worked on the interface and visuals.

How I gathered the evidence

I could not access large-scale player testing, so I used bots with different playing behaviours. I report their assumptions alongside the results and compare design changes under the same conditions. These are bot results. I have not yet checked whether real players respond to the changes in the same way.

ScopeTools
84 levels, 7 exhibitions · Android and iOSUnity 6 · C# · Figma · Blender
Analytics, remote settings, purchase and rewarded ad infrastructureFirebase Analytics · Crashlytics · Remote Config
Analysing the bot dataSQL · Excel
Menus, hints and rule cards in 10 languagesTurkish, English, Spanish, Portuguese, German, French, Italian, Russian, Indonesian, Vietnamese

Where does the name come from?

The French “à plomb” describes standing upright and balanced, like a plumb line. In English, aplomb means keeping your composure under pressure. I liked how it connects balance with taking a moment to think.

Gameplay

The basic rule is weight × distance from the centre. A weight of 3 placed two hooks away balances a weight of 2 placed three hooks away on the other side. Hovering a known-weight piece over a hook previews the tilt. Hidden “?” pieces have no preview.

Learning the order first

The video shows levels 6 and 11. On level 6, hanging the heavy piece at the far end causes a slip. Starting closer to the centre balances the same pieces safely. Level 11 introduces balancing a sub-arm before balancing the main arm.

  • Pieces can be moved between hooks or taken back into the tray.
  • The first five levels have no slips. Players explore weight and distance before placement order becomes a challenge on level 6.
  • A level ends when every arm is balanced and any rule cards are satisfied.

A guide to the game screen

A screen from level 44. The numbers identify the pieces and boosters discussed on this page.

Game screen of level 44: the gallery button, level name and brass counter at the top. A hanging structure with two sub-arms in the middle. A 1 and a hidden piece in the tray below it. The Scale, Clamp and Undo boosters at the bottom. The image has numbered markers from 1 to 10.
  1. 1Hanging structure. The level itself: a structure of arms hanging from one another, suspended from the top. The level ends when every arm is level at the same time.
  2. 2Tray. The pieces waiting to be hung. The player drags a piece from the tray onto a hook. Pieces that slip come back here.
  3. 3Piece. The number on it is its weight. The farther from the pivot it hangs, the harder it pulls on the arm.
  4. 4Arm. A bar hanging from one point (the pivot). It stays level when both sides pull equally. From level 6 on, it slips if it tips too far.
  5. 5Hook. The rings that pieces and sub-arms hang from. Each hook is 1, 2 or 3 units from the pivot.
  6. 6Sub-arm. An arm that hangs from another arm. To the arm above, it acts like a single piece as heavy as everything on it. It has to balance on its own first.
  7. 7Hidden piece. Its weight is hidden behind a “?” until it is hung. Arrives with the Glass exhibition (level 37).
  8. 8Boosters. Scale weighs a hidden piece, Clamp holds an arm steady for 3 placements, Undo brings back the pieces that just slipped. Clamp and Undo unlock at level 8, Scale at level 37.
  9. 9Brass counter. The in-game currency, earned by clearing levels. Tapping the counter opens the Shop.
  10. 10Gallery button. Opens the exhibitions, their rooms, level select and Settings.

Game loop

  1. LookWhich arm leans which way?
  2. WeighHold the piece over a hook and watch the arm react.
  3. HangLet go: the arm either gets closer to level or slips.
  4. FixSlipped pieces return to the tray and the order is rebuilt.

When pieces slip, they return to the tray. That attempt can no longer earn a slip-free finish, but you can keep playing. I wanted a mistake to give you a reason to reconsider the order and try another way.

Completed levels stay in the gallery as small hanging structures. Finish an exhibition’s 12 levels to open the next room and earn 150 brass. Complete each level without a slip at least once and the room lights up. Using boosters does not prevent this.

Level design

I divided the 84 levels into seven exhibitions of 12. Each has its own material and design focus. Some introduce new mechanics. The last combines familiar mechanics with a tighter slip tolerance.

Paper, level 11: a fixed 3 and a sub-arm hanging on the right. Two pieces left in the tray.
Paper Level 11
Wood, level 19: a three-arm hanging structure. Two blue 1s left in the tray.
Wood Level 19
Copper, level 28: a sub-arm on the main arm and below it the free arm the player hung. Two pieces left in the tray.
Copper Level 28
Glass, level 44: two sub-arms, a locked hook and a hidden piece in the tray.
Glass Level 44
Felt, level 53: the “Arm A not empty” card on top and the sub-arm marked A on the left.
Felt Level 53
Stone, level 66: a wind arrow at the right end of the arm and the “Side by side” card on top.
Stone Level 66
Neon, level 80: a pink neon arm, wind and a locked hook on the left. One of the two pieces left in the tray is a decoy.
Neon Level 80
Levels Exhibition New mechanic / design focus The player’s likely new question
1–12 Paper Weight × distance, sub-arms, slipping from level 6 on, pieces fixed to the arm Where does this piece go, and in what order?
13–24 Wood Locked hooks, arms with uneven sides (asymmetric), three-arm hanging structures How does a hook I can’t use change my plan?
25–36 Copper Free arms that the player hangs themselves. Decoys: not every piece in the tray is used Which piece doesn’t belong on this structure?
37–48 Glass Hidden weights: “?” pieces only show their weight once hung Where can I safely weigh a weight I don’t know?
49–60 Felt Rule cards: “Side by side”, “Red apart”, “Arm A left-heavy”, “Lowest”, “Arm A not empty” It balances, but does it follow the rule?
61–72 Stone Wind: a load that pushes one end of an arm and can’t be moved. The slip threshold drops from 6 to 5 How do I balance a load I can’t lift?
73–84 Neon Slip threshold 4 (6 on the two walls): arms slip much sooner. Every earlier rule comes back In what order can I hang this without a single slip?

Planning the rhythm before making levels

I start by deciding what the level should teach. I introduce the rule in a short level, then combine it with earlier ones. A harder checkpoint asks the player to use those lessons together. After that, I add a breather. Here are five examples from Glass.

LevelPurposeThe decision I want the player to make
37 · IntroductionIntroduce one hidden weightHang a “?” piece to reveal its weight
38 · EasyPractise weighing near the centreTry an unknown piece on a safe hook before putting it at the far end
42 · MediumCombine two hidden pieces with an asymmetric armThink about placement order while discovering weights
44 · CheckpointCombine hidden pieces, sub-arms, locked hooks and fixed weightsPlan the balance of multiple arms together
45 · BreatherEase the pressure after the checkpoint. Identify a hidden decoyDecide whether a revealed piece belongs on the structure

I set higher completion targets for introductions and lower ones for checkpoints. The rates and times below are design targets, not player measurements. I use them to plan the pressure across medium levels and breathers.

BandDifficulty scoreTarget: players who clear itTarget: median time
Tutorialbelow 395%45 s
Easy3–688%60 s
Medium6–980%90 s
Wall9–1260%150 s
Rejectedabove 12not shipped–

In the first exhibition, the teaching purpose takes priority over the score. Level 6 scores 5.2, but I treat it as an introduction because it teaches slipping for the first time. I will revisit the time targets in player tests.

Designing and refining the levels

For each level, I chose a teaching goal, the mechanics to combine and its place in the exhibition. I checked the design with the solver and bots, then revised levels that missed their target or needed another look.

The editor lets me change hooks, weights, sub-arms and rule cards. I can then compare balanced placements and safe orders. I also open the level in the game to check that its lesson and layout still make sense.

Screenshot of the level editor in Unity: level 84’s settings on the left, the structure preview and the solver result on the right.
The level editor inside Unity, with the game’s last level open. On the left: a locked hook, wind, a sub-arm on the right, four pieces (one of them hidden) and a “Lowest” card. On the right, the preview and the solver: there are 4 balanced placements, and in each of them 20 of the 24 hanging orders finish without a slip.
  1. Build: I arrange hooks, weights, sub-arms and mechanics around the learning goal.
  2. Check: I inspect balanced placements and safe orders, then compare how an edit changes the problem.
  3. Play and revise: I open the unsaved level in the game to check readability and feedback, then review the bot result against the target before saving.

The illustrated editor workflow

  1. I start with the level’s purpose. I define the decision I want the player to make and which earlier lessons it should use. For a new mechanic, I choose a clear introduction. For a checkpoint, I choose the rules to test together.
  2. I set up the hanging structure and the tray. I type the hooks separated by commas, or use the ±1…±4 buttons for an arm with equal sides. Deleting a hook makes the arm asymmetric. Locked hooks, wind that pushes the arm one way, pieces fixed to the arm and sub-arms are set here too. In the tray, every piece has a weight, a shape, a colour and a hidden toggle. I also set how many pieces must be hung. If the tray holds more, the extras are decoys. Rule cards come in five types: side by side, a colour apart, arm left-heavy, lowest, and arm not empty. If I build something invalid (locking a hook that doesn’t exist, or hanging a sub-arm on a hook that’s taken), an error appears under the preview.
    Level 42’s arm and tray settings in the level editor: hooks -3, -2, -1, 1, 3. Four pieces, two of them hidden.
    Level 42’s arm and tray. The arm has no +2 hook, which makes it asymmetric. Two of the four pieces in the tray are hidden.
  3. I run the solver. In this example, “Solve” takes 3 milliseconds. The verdict is at the top: can the level ship, which band is it in, P, S, R and the difficulty score (D). Below it are a sample slip-free order and every solution. Next to each solution is how many of all the possible hanging orders finish without a slip. The sample order is also drawn on the preview: “2:3” means the 3 goes on that hook in the second step.
    The solver panel: PUBLISHABLE, band orta, P=120 S=6 R=0.67 D=8.3. Four numbered rings in the preview.
    Level 42 today: the medium band (“orta” in the editor), score 8.3. All six solutions are the same layout. Only the three 2s swap places. In each of them, 16 of the 24 orders finish without a slip.
  4. I check what a change does. Changing a setting clears the order from the preview, so I press “Solve” again and look at the new numbers. Below is the same level before the bot test. There are two differences: the arm has no +1 hook, and the hidden diamond weighs 4, not 3. In this version the only place for the 4 is the +3 hook, and in each solution only 4 of the 24 orders finish without a slip. The solver could see that most orders slip (R = 0.17), but not that revealing a hidden weight shapes the player’s decisions. With a score of 7.5, the level sat in the medium band. The bots showed how hard it really was (in the level 42 case study).
    The solver panel for the old version of level 42: P=24 S=6 R=0.17 D=7.5. Safe 4/24 for each solution.
    The same level before the bot test: P = 24, R = 0.17, score 7.5. In the sample order the 2 goes to −3 first, then the 4 to +3: without a load of 6 on the left, the 4 tips the arm.
  5. I try it in the game. “▶ Play” opens the editor’s current version in the game scene, without saving. The solver says the level can be solved and how hard it is on paper, not how it looks on screen: whether the structure is easy to read, how far the arm tilts, whether the hidden pieces stand out at first glance. Those I check in the game.
    Left, level 42’s preview in the level editor. Right, the same level on the phone’s game screen.
    Left, the preview in the editor. Right, the same level in the game.
  6. I save the level with its report. Save checks the structure and runs the solver again. The level file stores its band, P, S, R and score. Revert restores the last saved version.

How I use the difficulty score

The solver counts balanced placements and checks which orders avoid slips. I use those results, along with hidden weights, decoys, cards and sub-arms, to calculate a difficulty score. The score helps me compare designs. It cannot tell me everything a player knows or might misunderstand.

I chose the formula’s coefficients myself. They have not been fitted to player data. I read the score alongside bot results and the level’s teaching goal. Level 42 is an example of those signals disagreeing.

Difficulty scoring and lesson checks

Solver.cs · the difficulty score
int hidden = def.Tray.Count(p => p.Hidden);
int decoys = Math.Max(0, n - target);
int cards = def.Constraints?.Count ?? 0;
int depth = MobileState.FromDef(def).MaxDepth;
result.D = Math.Log((double)result.P / result.S, 2)
           + 3.0 * (1.0 - result.R)
           + 1.5 * hidden
           + 1.0 * decoys
           + 1.5 * cards
           + 1.0 * (depth - 1)
           + 1.0 * freeCount;
result.Band = BandFor(result.D);
result.Publishable = result.S >= 1 && result.R > 0 && result.D <= 12.0 && !truncated;

P is possible placements, S balanced placements and R the share of safe orders. Distinct pieces with equal weights are counted separately, so S is not always the number of meaningfully different solutions.

I reject candidates with no solution, R = 0, a score above 12 or an exhausted search budget. Large order searches use sampling. The technical section explains the limits.

LevelChecks.cs · the “weigh near the centre” check
public static bool TeachesWeighNearPivot(LevelDef def)
{
    foreach (var hidden in def.Tray.Where(p => p.Hidden))
    {
        bool nearSafe = false, farSlips = false;
        foreach (int hook in def.Root.Hooks)
        {
            var st = MobileState.FromDef(def);
            st.Pieces[hidden.Id].Revealed = true;
            var r = st.Preview(hidden.Id, st.Root.Id, hook);
            if (!r.Available) continue;
            if (Math.Abs(hook) == 1 && !r.WouldSlip) nearSafe = true;
            if (Math.Abs(hook) >= 2 && r.WouldSlip) farSlips = true;
        }
        if (nearSafe && farSlips) return true;
    }
    return false;
}

The near-centre check looks for a hidden piece that can be weighed safely on a nearby hook but slips on a distant one. That confirms the setup supports the lesson. I still need to observe players to see whether they discover it.

Protecting the intended lesson

The drag hint on the first level uses a safe move found by the solver. Short hints introduce slipping, hidden weights, cards and wind. If every piece is hung but the structure is still unbalanced, a reminder appears after 4.5 seconds explaining that hung pieces can be moved.

I turned Paper’s intended solution orders into automated checks. On level 6, putting the heavy piece at the far end must cause a slip. An edit that removes that teaching moment breaks the test. Copper’s introduction must require the loose arm. In Felt, removing the cards must increase the solution count. This checks their combined effect, rather than proving that every card matters individually.

Difficulty curve

The chart shows difficulty scores after the 15 level revisions. Colours mark the bands. Dots mark breathers immediately after checkpoints. I chose an uneven curve, easing the pressure when introducing a rule and when giving players room to practise after a hard level.

  • Tutorial
  • Easy
  • Medium
  • Wall
  • Breather level (right after a wall)

Difficulty score

Chart data
LevelExhibitionBandScore
1Papertutorial2.6
2Papertutorial0.0
3Papereasy4.9
4Papereasy5.9
5Papertutorial2.6
6Papertutorial5.2
7Papermedium7.2
8Papereasy2.6
9Papereasy4.4
10Papermedium6.4
11Papermedium7.2
12Paperwall10.8
13Woodtutorial1.6
14Woodeasy5.3
15Woodeasy4.0
16Woodmedium7.0
17Woodeasy5.9
18Woodmedium7.5
19Woodmedium7.6
20Woodwall10.6
21Woodeasy · breather4.4
22Woodmedium7.3
23Woodmedium7.5
24Woodwall10.6
25Coppertutorial1.0
26Coppereasy5.1
27Coppereasy5.0
28Coppermedium8.7
29Coppereasy4.5
30Coppermedium7.7
31Coppermedium7.4
32Copperwall10.5
33Coppereasy · breather4.5
34Coppermedium7.6
35Coppermedium7.6
36Copperwall10.6
37Glasstutorial1.5
38Glasseasy5.4
39Glassmedium7.6
40Glassmedium7.6
41Glasseasy4.8
42Glassmedium8.3
43Glassmedium7.5
44Glasswall10.5
45Glasseasy · breather5.3
46Glassmedium7.6
47Glassmedium7.9
48Glassmedium7.5
49Felteasy6.0
50Feltmedium8.2
51Felteasy5.8
52Feltmedium8.0
53Feltmedium8.5
54Feltwall10.8
55Felteasy · breather6.0
56Feltmedium8.9
57Feltmedium8.9
58Feltmedium7.9
59Feltmedium7.9
60Feltwall10.5
61Stoneeasy4.4
62Stoneeasy4.6
63Stonemedium7.5
64Stoneeasy4.9
65Stonemedium7.6
66Stonemedium7.4
67Stonemedium7.5
68Stonewall9.3
69Stoneeasy · breather4.7
70Stonemedium7.1
71Stonemedium8.4
72Stonemedium8.7
73Neoneasy4.6
74Neonmedium7.5
75Neonmedium7.5
76Neonmedium7.4
77Neonmedium8.0
78Neonwall11.9
79Neoneasy · breather5.5
80Neonmedium7.4
81Neonmedium7.4
82Neonmedium8.4
83Neonmedium7.6
84Neonwall11.0

Level 42: problem, decision, result

Level 42 combines two hidden pieces with an asymmetric arm. I placed it in the medium band, with an 80% completion target. Only 7% of bots finished it, and the experienced profile reached just 3%. I went back to the layout rather than trusting its score.

Old layout

The old version of level 42: an arm with a single hook on the right. In the tray a star 2, two hidden pieces and a black 2.
One hook on the right (+3). Two pieces are hidden.
The hidden piece hung at +3 makes the arm slip. A red “Slip” appears and the piece returns to the tray.
Hung at +3, the “?” turns out to be a 4: 4 × 3 = 12, threshold 6. The arm slips.
The old layout one step before the solution: two 2s on the left, the 4 on the right.
One of the safe orders: 2s on the left first, then the 4 at +3.

7% of bots cleared it

New layout (in the game)

The new version of level 42: +1 and +3 hooks on the right. In the tray a star 2, two hidden pieces and a black 2.
There’s a +1 hook on the right. The hidden 4 became a 3.
The new layout one step before the end: a 3 and a 2 on the left, a 2 on the right. The last black 2 in the tray.
Before the last piece: the left pulls 8, the right 2. The gap is 6 and the arm holds.

82% of bots clear it

Two versions of the same level. The screenshots are from the game itself. The old layout was opened from the backed-up level file.

The problem: unknown weights and a narrow order

The hidden 4 could only occupy +3 in the final arrangement. It pulls with 12 units of torque there, against a slip threshold of 6. Hanging it too early causes a slip, but its weight is unknown until it is hung. For each balanced placement, only 4 of the 24 orders were safe.

The old layout allowed a piece to be weighed near the centre, then moved or removed. The bots struggled to discover the weights while following a narrow safe order. I used that result to revisit the design. I have not measured whether people struggle at the same point.

The change: keep the idea, widen the safe options

I added a +1 hook and changed the hidden 4 to a 3. The two hidden pieces and asymmetric arm remained. My aim was to make it more suitable for its medium-band role without requiring Scale. Safe orders increased from 4/24 to 16/24.

Final arrangement: 3 → −2, 2 → −1, 2 → +1, 2 → +3. This is a placement map, not a move sequence. Once the weights are known, one safe order is:

StepMoveTotal arm torque
13 → −2−6
22 → +1−4
32 → −1−6
42 → +30

No step exceeds an absolute torque of 6. This demonstrates a safe way to build the solved arrangement. It does not describe the process of discovering the hidden weights.

The result and what I learned

Old layoutNew layout
Hooks−3, −2, −1, +3−3, −2, −1, +1, +3
Pieces2, 2, hidden 2, hidden 42, 2, hidden 2, hidden 3
Possible placements / balanced24 / 6120 / 6
Orders that finish without a slip4 of 2416 of 24
Difficulty score7.58.3
Mixed completion7%82%
Novice / casual / expert10% / 6% / 3%72% / 82% / 98%

The initial comparison used the same level test for both layouts: three bot profiles and three random seeds for each layout. Each profile ran 300 times per seed, giving 900 runs per profile and 2,700 per layout. The previous and revised layouts together account for 5,400 runs. The table shows rounded results from that first measurement. The solver’s six balanced placements come from exchanging three equal-weight pieces. They represent one arrangement of weights across hooks.

The score rose from 7.5 to 8.3, while bot completion rose from 7% to 82%. Adding a hook increased possible placements and raised the score. More safe orders made the level easier for the bots. This reminded me to look at when information becomes available, as well as how many options a level has.

In a separate check, I varied misdrop probability, patience and attention to the preview one at a time. Across seven settings, mixed completion ranged from 6.8–7.5% on the previous layout and 82.0–82.9% on the current one. Each of the three bot profiles improved. The change held up under the settings I tested. Its effect on real players remains unmeasured.

Tests and improvements

I tested levels individually, then ran all 84 in sequence. The first test helped me inspect each design. The second showed where bots stopped progressing. I compared revisions under the same conditions.

Bot profiles and test method

I use novice, casual and experienced profiles, weighted at 30%, 50% and 20% in the mixed result. I chose the mixture, error rates and patience limits. They did not come from player data. Novices check the preview less often. Experienced bots look two moves ahead.

The profiles share a decision model that scores moves by their contribution to balance. They do not know hidden weights until hanging them and use Undo, but not Clamp or Scale. They know how to move and remove pieces from the start, and the novice profile does not learn across levels. These results therefore do not assess tutorial comprehension, enjoyment or next-day return.

  • Novice (30%). Scattered choices, drops 8% of pieces on the wrong hook, looks at the preview about half the time, often forgets the cards, rarely thinks of weighing a hidden piece near the centre. Patience: 3 minutes.
  • Casual (50%). Drops 5% of pieces on the wrong hook, usually checks the preview, uses Undo after about half of its slips. Patience: 4 minutes.
  • Expert (20%). Thinks two moves ahead, almost always checks the preview, always follows the cards, usually weighs a hidden piece near the centre first. Patience: 6 minutes.

Tests and measures in this section

Level test
Each profile plays each level 300 times with each of three random seeds: 900 runs per profile and 2,700 for each layout of a level. This shows how hard a level is on its own. The charts and change table come from this test.
Mixed completion
The share of runs that clear the level, weighted novice 30%, casual 50%, expert 20%.
Off band
A level is off band if its mixed completion is more than 10 points away from its band’s target.
Play-through test
4,000 bots, in four groups of 1,000, start at the first level and play in order. A bot stops if it cannot clear a level in two sessions. I use this test to inspect progression and the brass economy.
Bot A/B test
10,000 bots are assigned to two groups using a value calculated from their IDs. Each group uses its own Remote Config settings and follows the sequential test’s rules.
Rank correlation
How closely the difficulty-score ranking matches the completion ranking (Spearman). −1 means completion always drops as the score rises.

What the first test revealed

In the initial layouts, 12 levels fell more than 10 percentage points below their completion target, while 46 exceeded it. Some intended easy and medium levels were harder for the bots than checkpoints.

  • 42 and 70: Both were medium levels with an 80% target. Bot completion was 7% and 8%.
  • 14, 15 and 17: Easy levels targeted 88%, but completion ranged from 36% to 45%.
  • 12: The first exhibition’s finale. In sequential simulation, 51.7% of bots reaching it stopped there. 47.6% of the starting cohort reached level 13.
  • 68, 78 and 84: Intended checkpoints were completing well above target.

Completion chart for all 84 initial layouts

  • More than 10 points below target
  • Close to target
  • More than 10 points above target
  • Breather level (right after a wall)

Mixed completion

Chart data
LevelBandCompletionTarget
1tutorial100%95%
2tutorial100%95%
3easy100%90%
4easy96%88%
5tutorial98%95%
6tutorial100%92%
7medium97%80%
8easy99%88%
9easy100%88%
10medium97%80%
11medium94%80%
12wall28%60%
13tutorial100%95%
14easy43%88%
15easy36%88%
16medium77%80%
17easy45%88%
18medium54%80%
19medium98%80%
20wall36%60%
21easy · breather100%88%
22medium96%80%
23medium100%80%
24wall62%60%
25tutorial98%95%
26easy100%88%
27easy99%88%
28medium95%80%
29easy100%88%
30medium100%80%
31medium87%80%
32wall63%60%
33easy · breather100%88%
34medium100%80%
35medium99%80%
36wall32%60%
37tutorial100%95%
38easy100%88%
39medium100%80%
40medium100%80%
41easy100%88%
42medium7%80%
43medium99%80%
44wall74%60%
45easy · breather100%88%
46medium100%80%
47medium44%80%
48medium100%80%
49easy97%88%
50medium93%80%
51easy93%88%
52medium100%80%
53medium89%80%
54wall60%60%
55easy · breather94%88%
56medium84%80%
57medium82%80%
58medium65%80%
59medium97%80%
60wall69%60%
61easy100%88%
62easy100%88%
63medium99%80%
64easy95%88%
65medium98%80%
66medium89%80%
67medium98%80%
68wall92%60%
69easy · breather100%88%
70medium8%80%
71medium92%80%
72medium100%80%
73easy100%88%
74medium100%80%
75medium98%80%
76medium84%80%
77medium60%80%
78wall80%60%
79easy · breather100%88%
80medium97%80%
81medium83%80%
82medium98%80%
83medium75%80%
84wall85%60%

Small, purposeful changes to 15 levels

My search tool tries single and paired edits to weights, hooks, sub-arms, fixed weights and wind. The solver rejects invalid designs, then bots test the rest. I chose changes that moved closer to the target while keeping the lesson. I made 12 levels easier and three overly easy checkpoints harder.

What changed in the 15 levels?

LevelChangeBandMixed completionTarget
12 · Paper Added a +2 hook to the sub-arm. wall 28% → 59% 60%
14 · Wood Unlocked the +3 hook. The 4 became a 3. easy 43% → 98% 88%
15 · Wood Moved the sub-arm from −1 to +2. easy 36% → 99% 88%
17 · Wood Added a +3 hook to the arm. easy 45% → 85% 88%
18 · Wood Moved the sub-arm from +1 to −1. medium 54% → 75% 80%
20 · Wood Moved the left sub-arm from −2 to −1 and gave it a +2 hook. wall 36% → 58% 60%
36 · Copper Added a −3 hook to the arm and locked the free arm’s −1 hook. wall 32% → 56% 60%
42 · Glass Added a +1 hook. The hidden 4 became a 3. medium 7% → 82% 80%
47 · Glass Added a −2 hook. One of the two hidden pieces is now shown. medium 44% → 68% 80%
58 · Felt Moved the locked hook from +1 to +2. medium 65% → 72% 80%
70 · Stone The fixed piece at −1 went from 5 to 4. medium 8% → 87% 80%
77 · Neon One of the 1s became a 2. medium 60% → 85% 80%
68 · Stone Wind on the main arm went from 2 to 1. One of the 3s became a 2. wall 92% → 56% 60%
78 · Neon The 2 became a 3. wall 80% → 63% 60%
84 · Neon Moved the sub-arm from −1 to +3. wall 85% → 64% 60%

Each row is a level I changed and shows how far it is from its own target. The middle line is the target. The green area is within 10 points of it. The arrow goes from the level before the change to the level today.

  • Before the change
  • Today (in the game)
  • Within 10 points of target

Chart data

LevelBeforeAfterTarget
12 · Paper28%59%60%
14 · Wood43%98%88%
15 · Wood36%99%88%
17 · Wood45%85%88%
18 · Wood54%75%80%
20 · Wood36%58%60%
36 · Copper32%56%60%
42 · Glass7%82%80%
47 · Glass44%68%80%
58 · Felt65%72%80%
70 · Stone8%87%80%
77 · Neon60%85%80%
68 · Stone92%56%60%
78 · Neon80%63%60%
84 · Neon85%64%60%

Percentages in the charts and tables are rounded. Level 14 is at the upper boundary. Level 15 is still too easy and level 47 too hard. Moving closer to a target does not by itself prove that a level works well for players.

After retesting

  • Levels far below target fell from 12 to 1. Level 47 remains below: 68% against an 80% target.
  • The rank correlation between difficulty score and bot completion strengthened from −0.50 to −0.65. That is a more consistent ranking in this model, awaiting validation with people.
  • In sequential simulation, reaching level 13 rose from 47.6% to 81.8%, and finishing the game rose from 0% to 14.2%.
  • The largest stopping points are now mostly intended checkpoints: 20, 36, 12, 68 and 24. In Glass, level 47 is still slightly harder than its checkpoint.
  • Before the changes
  • After the changes

Chart data

ReachedBeforeAfter
Level 13 · Wood47.6%81.8%
Level 25 · Copper5.1%49%
Level 37 · Glass2.3%34.4%
Level 49 · Felt0.08%28.7%
Level 61 · Stone0.05%21.3%
Level 73 · Neon0%18%
Finished the game0%14.2%

I applied the initial revisions to the game’s level files. The charts show that first iteration within the same bot model. I then checked selected levels separately with different behavioural settings and new random seeds. Those results appear below.

Do the results hold when behaviour settings change?

On 9 October 2026, I reran the existing three profiles. In addition to their baseline settings, I halved misdrop probability and increased it by 50%, set patience to 75% and 125%, and lowered preview attention by 20 percentage points or raised it by 15. I changed one setting at a time and kept attention within 0–100%.

I used seven scenarios, three new random seeds and 200 runs per profile for each seed: 600 runs per profile for each layout and scenario, and 63,000 level attempts overall. Profile settings, Undo stock and the starting seeds for each run were matched between the previous and current layouts.

LevelPrevious layoutCurrent layoutFinding
426.8–7.5%82.0–82.9%Each profile improved under every tested setting.
707.4–7.9%84.9–88.5%Reducing the fixed weight helped under every tested setting.
47Not compared64.7–66.4%Below its 80% target under every tested setting.

These ranges are the lowest and highest completion rates across seven settings using the 30% novice / 50% casual / 20% experienced mixture. They are not confidence intervals or predictions for people. I also reweighted the results for novice-heavy (60%/30%/10%) and experienced-heavy (10%/40%/50%) mixtures. Levels 42 and 70 still improved. Level 47 remains a revision priority.

I reconstructed the previous layouts of 42 and 70 from the recorded change notes. The game’s current level files and bot code stayed unchanged. I did not reconstruct level 47 because the notes do not identify its second originally hidden piece. This checks consistency within the behaviour settings I tested. It does not validate human behaviour.

Profile results and repeat settings

For each of the seven settings, I combined three seeds: 202610091, 202610092 and 202610093. Each seed has 200 runs per profile, giving 600 per profile and setting. The ranges below are the lowest and highest completion rates across those seven settings after combining the seeds. They are not confidence intervals.

Level / versionNoviceCasualExpert
42 · earlier8.5%–10.0%7.3%–8.8%2.0%–2.7%
42 · current70.3%–74.8%81.0%–83.5%99.3%–99.5%
70 · earlier12.2%–13.3%7.0%–7.7%1.0%–1.3%
70 · current69.0%–75.7%89.3%–93.2%97.2%–98.5%
47 · current48.5%–52.3%65.2%–68.0%83.2%–84.2%

Preview stays on, the slip-threshold bonus is zero and the starting Undo stock is 3. Other profile settings remain fixed. Each run starts with seed + level × 10007 + profile_index × 1009 + trial × 17. Random sequences can diverge after different moves. Recorded seconds are modelled bot time, not measured player time. Changing one setting at a time does not cover all combinations or other decision models.

Preview: A/B test with bots

The placement preview shows how an arm will tilt before you release a piece. I compared preview on and off using the existing bots. I used the test to check group assignment, measurement and analysis as well as the design choice.

Hypothesis and experiment plan

My hypothesis was that removing the preview could increase slips and Undo demand, and reduce completion of the first exhibition. Reaching level 13 was the primary metric. Undos bought with brass were an economy guardrail.

  • Assignment: I assigned 10,000 bots using a stable hash of their IDs. The control had preview on, and the other group had it off.
  • Analysis: I wrote the plan before the experiment. I used two-sided tests at α = 0.05 and 95% intervals calculated at bot level. Completion proportions use a normal approximation, per-attempt ratios use 2,000 bot-level bootstrap repetitions, and means use Welch tests.
  • Data check: I first ran an A/A test with preview on in both groups. Small deviations from 50/50 are expected. An SRM chi-square result of p < 0.001 requires investigating assignment before using the outcome.

The A/A test assigned 5,021 and 4,979 bots, with no sample-ratio issue or significant metric differences. I found no clear problem in assignment or analysis in that run. This check does not tell me how closely the bots resemble people. The main test assigned 4,984 and 5,016 bots.

Metric Preview on Preview off Difference (95% CI)
Bots reaching level 13 (primary) 82.6% 81.2% −1.4 points (−2.9 to +0.1)
Levels cleared per bot 36.7 36.4 −0.3 (−1.3 to +0.7)
Slips per attempt 0.103 0.261 +0.159 (+0.154 to +0.164)
Levels cleared in the first session 92.34% 92.37% +0.02 points (−0.16 to +0.20)
Bots finishing the game 13.8% 14.4% +0.6 points (−0.8 to +1.9)
Undos bought with brass, per bot 0.30 2.44 +2.14 (+2.04 to +2.24)

Difference = preview off − on. Differences between proportions are percentage points. Displayed rates and means are rounded. Differences use unrounded values. All results are simulated.

How I read the result

With the preview off, slips per attempt rose roughly 2.5-fold and Undos bought with brass increased from 0.30 to 2.44 per bot. Reaching level 13 fell by 1.4 percentage points, with an interval from −2.9 to +0.1 and p = 0.07. This does not establish equivalence: both a small decline and a near-zero difference fit the result.

Turning the preview off makes the bot give slip risk less weight when choosing a move. It still calculates balance from the game code. This test measures that change in the bot’s decision model. It does not fully reproduce how a person reasons without a preview.

I kept the preview on by default. Turning it off increased slips and Undo demand, while the result for reaching level 13 was uncertain. More booster use alone was not a reason to choose it. Keeping the preview also supports the lesson I want to teach. I have not measured its effect on real players.

Additional checks, sample planning and implementation

In a paired check added after the plan, I ran the same 10,000 bot scenarios under both settings. The difference in reaching level 13 was −1.6 percentage points (95% interval: −2.7 to −0.6), suggesting a small effect within the model. Uncertainty in the main test and the limits of representing human behaviour remain.

I also rehearsed sample-size planning: using that effect, a baseline near 82%, a two-sided α = 0.05 test and 80% power gives roughly 8,800 players per group, or 34,600 if the effect halves. These are simulation-based scenarios. I cannot currently access that many players, so I am not planning a live experiment at this scale. If that becomes possible, the calculation would need real pilot data and a defined smallest worthwhile effect.

An exploratory novice breakdown fell from 71.9% to 67.8% reaching level 13 (p = 0.01). This comparison was not planned beforehand, so I treat it as exploratory. I will define profile breakdowns and comparisons before running a new experiment.

Bots read remote settings through the same Tuning code as the game. Disabling the preview changes the weight of slip risk in the move score. Balance calculations continue. During the study, I fixed a threshold-bonus bug that could enable slipping in the first five levels and added a regression check.

PlayerBot.cs and BotAbTest.cs · the preview setting and group assignment
// Remote Config can switch the preview off (preview_enabled); then the bot can't look at it either.
bool watching = Tuning.PreviewEnabled && rng.NextDouble() < p.PreviewAttention;

// Firebase A/B Testing splits real players on its own servers. Bots get the same kind of split:
// a bucket from a hash of the experiment name and the player id, so a player never changes group.
public static int Bucket(string experiment, int playerId)
{
    uint h = 2166136261;
    foreach (char c in experiment + ":" + playerId) { h ^= c; h *= 16777619; }
    return (int)(h % 100);
}

Plan and the analysis I ran

I wrote the initial plan on 8 October, before the run. Assignment uses FNV-1a on h2:<bot ID>, modulo 100: values 0–49 go to preview on, 50–99 to preview off. The simulation uses seed 20261008, the current 84 levels, a 30% novice / 50% casual / 20% expert mix and at most two sessions per level. The original plan proposed bootstrap for means too. In the analysis I used Welch tests for means, while keeping the 2,000 bot-level bootstrap repetitions for per-attempt ratios. I report that change with the result.

SQL and Excel

In the preview A/B test, 10,000 bots played 402,495 attempts. I put this data into three simple tables and asked questions with SQL. The queries run in SQLite.

  • players: one bot per row: its group, profile, how many levels it cleared, the level where it quit, slips, Undo bought with brass, and brass earned and spent.
  • attempts: one attempt per row: the level, the attempt number, whether it was won, slips and Undo uses.
  • levels: each level’s exhibition and band. Bands are stored as tutorial, easy, medium and wall.

1. What changes when the preview is turned off?

SQL · A/B test data
-- Önizleme açık ve kapalı gruplar nasıl karşılaştırılıyor?
SELECT ab_group,
       COUNT(*) AS bots,
       ROUND(100.0 * SUM(CASE WHEN levels_cleared >= 12 THEN 1 ELSE 0 END) / COUNT(*), 1) AS reached_13_pct,
       ROUND(1.0 * SUM(slips) / SUM(attempts), 3) AS slips_per_attempt,
       ROUND(AVG(undo_bought), 2) AS undo_bought_avg
FROM players
GROUP BY ab_group
ORDER BY ab_group DESC;
ab_groupbotsreached_13_pctslips_per_attemptundo_bought_avg
h2_preview498482.60.1030.3
h2_no_preview501681.20.2612.44

What the query does: It splits the players table into the two groups: preview on (h2_preview) and off (h2_no_preview). For each group it calculates the number of bots, the share that reached level 13, slips per attempt and the average Undo bought with brass. CASE WHEN counts a bot that cleared 12 or more levels as 1 and any other bot as 0.

What I found: With the preview off, slips rise about 2.5-fold and Undos bought with brass go from 0.30 to 2.44 per bot. Reaching level 13 falls from 82.6% to 81.2%, a 1.4-point difference. The statistical check in the A/B section leaves that difference uncertain.

2. On what kind of level do bots quit?

SQL · A/B test data
-- Botlar oyunu hangi tür level'da bırakıyor? (önizleme açık grup)
SELECT l.band,
       COUNT(*) AS bots_quit
FROM players p
JOIN levels l ON l.level = p.stopped_at_level
WHERE p.ab_group = 'h2_preview' AND p.finished = 0
GROUP BY l.band
ORDER BY bots_quit DESC;
bandbots_quit
wall3212
medium963
easy114
tutorial5

What the query does: From the preview-on group, it takes the bots that did not finish. The JOIN finds the level where each bot quit in the levels table and brings its band. Then it counts the bots that quit in each band.

What I found: Of the 4,294 bots that stopped, 3,212 (75%) stopped at a wall. Only 11 of the 84 levels are walls. Very few stopped at easy levels. In this model, the bottlenecks fall mostly where I planned the harder checkpoints.

3. How much brass does a bot earn, and how much does it spend?

SQL · A/B test data
-- Bir bot ortalama ne kadar pirinç kazanıyor, ne kadar harcıyor? (önizleme açık grup)
SELECT bot_profile,
       COUNT(*) AS bots,
       ROUND(AVG(brass_earned)) AS earned_avg,
       ROUND(AVG(brass_spent)) AS spent_avg
FROM players
WHERE ab_group = 'h2_preview'
GROUP BY bot_profile
ORDER BY earned_avg;
bot_profilebotsearned_avgspent_avg
novice1514964.012.0
casual24741798.017.0
expert9962237.01.0

What the query does: It splits the preview-on bots by profile and averages the brass earned and spent.

What I found: Casual bots earn an average of 1,798 brass and spend 17. Experienced bots earn 2,237 and spend 1. Brass builds up faster than it is spent in this scenario. That gives me a reward and booster-use question to investigate. These bots buy Undo only and do not use Clamp or Scale.

Excel workbook

I also prepared the same data in Excel. Each row on the Players sheet is a bot, and the other sheets are calculated from it. COUNTIFS counts the rows that meet the conditions, and AVERAGEIFS averages them. The AB test sheet compares the two groups for all bots and for each profile: among novice bots, reaching level 13 falls from 71.9% to 67.8%. The Funnel sheet shows how many bots start each level, and the Economy sheet shows brass by exhibition.

Excel · two formulas
Funnel!D2 · bots starting a level = bots that cleared the level before it (preview on)
=COUNTIFS(Players!B:B, "h2_preview", Players!D:D, ">="&(A2-1))
AB test!G3 · average Undo bought with brass for the profile in A3 (preview on)
=AVERAGEIFS(Players!I:I, Players!B:B, "h2_preview", Players!C:C, A3)
The AB test sheet in Excel: bots, reaching level 13, the difference and Undo bought with brass for both groups, for all bots and each profile, with a column chart on the right.
The AB test sheet in Excel. The gap is largest among novice bots. Select the image to enlarge it.

All tables in this section come from bot data, not from real players.

Analytics and product decisions

I added Firebase Analytics, Crashlytics and Remote Config so I can investigate level and economy questions when player data becomes available. The results on this page come from local bot tests.

From data to a decision

Signal to investigateData to inspectA possible response
Exits cluster after the first sliparm_slip, level_quit, t_since_last_slipReview slip feedback and the follow-up hint
Hidden pieces are repeatedly hung at the far endpiece_hang, arm_slipStrengthen the near-centre weighing lesson in the layout
Booster demand spikes on one levelbooster_use, level_completeReview its band, information demands and safe orders
Shop opens do not lead to completed purchasesshop_open, iap_*Separate offer timing and value from technical failures
Exits increase at low frame ratesLevel events, FPS and CrashlyticsCompare device groups on the same level and investigate performance

The table connects the events I record to questions I want to investigate. Events appearing together do not prove that one caused the other. I would use observation and a suitable experiment to check the explanation.

Event list and measurement details

There are more than 30 events. They are also written locally, so I can inspect them in the editor and tests without Firebase. The main events for level design and economy are:

Event When Parameters The question it answers
level_start A level opens level, attempt number, replay or not, band, brass How far do players get?
piece_hang A piece is hung weight, hidden or not, hook, arm, torque after the move Where do players hang pieces?
arm_slip An arm slips arm, torque at the slip, pieces dropped Where and when do slips happen?
booster_use A booster is used type, slips so far, source At what moment do players reach for help?
level_complete A level is cleared time, slips, boosters, hangs/removes/moves, brass earned, frame rate How hard is a level and how long does it take?
level_quit A level is abandoned time, slips, remaining imbalance, time since the last slip, reason Do players quit right after a slip?
shop_open
iap_*
ad_rewarded_*
Shop and ads context, product, price, level Where does the purchase and ad funnel break?
config_applied Remote settings applied experiment group, source of the settings Which player played with which settings?

The custom level_quit event excludes exits without a move and backgrounding during ad or purchase flows. I will also inspect starts with no interaction, rather than building the new-player funnel from level_quit alone.

Time since the last slip and frame rate add context to exits. Performance data can reveal a possible issue, but does not explain the cause by itself.

AnalyticsService.cs · when a level is abandoned
private void Quit(string reason)
{
    var s = Session.State; var d = Session.Current;
    if (s == null || d == null) return;
    if (s.HangCommands == 0 && s.RemoveCommands == 0 && s.MoveCommands == 0) return;
    Analytics.Track("level_quit",
        ("level_id", d.Id), ("duration", Round(Session.LevelSeconds)), ("slips", s.SlipCount),
        ("abs_tau_state", Math.Abs(s.Torque(s.Root))), ("hung", s.HungCount),
        ("t_since_last_slip", _lastSlipAt < 0 ? -1f : Round(Session.LevelSeconds - _lastSlipAt)), ("reason", reason), Fps(), SlowShare(), FpsTarget());
}

Managing experiment settings

Remote Config controls preview, reveal delay, slip-threshold bonus, review prompts, frame rate and experiment group. Prices and levels remain fixed. Values are clamped to safe ranges, and config_applied records which settings took effect.

Setting limits, consent and test devices

SettingRange
preview_enabledOn / off
reveal_delay_s0.2–1.2 seconds
slip_threshold_bonus0–4
review_prompt_enabledOn / off
frame_rate_modeAuto / 60 / 30
ab_groupExperiment group label
Tuning.cs · the limits of each setting, shortened
public static bool PreviewEnabled => GetBool(KeyPreviewEnabled);
public static float RevealDelaySeconds => Clamp(GetFloat(KeyRevealDelay), 0.2f, 1.2f);
public static int SlipThresholdBonus => (int)Clamp(GetInt(KeySlipThresholdBonus), 0, 4);
public static bool ReviewPromptEnabled => GetBool(KeyReviewPromptEnabled);

// The bonus only raises a threshold that is already on: a level without slipping stays without it.
public static int SlipThresholdFor(int levelThreshold) => levelThreshold > 0 ? levelThreshold + SlipThresholdBonus : 0;

Fetched settings are cached on the device. When a cache exists, new settings apply on the next launch, keeping rules stable within a session.

Data permissions start disabled. A consent form is used where required, and permissions are passed to Firebase and ad requests. iOS tracking permission is handled after the consent form and before loading ads.

Analytics is disabled on Test Lab devices. Crashlytics reports include a test-scenario label. Bots do not connect to Firebase. Their results stay in local files, separate from any future player records.

Economy and monetisation

Boosters affect difficulty without placing pieces on the correct hooks. Undo reduces the cost of a slip, Scale reveals a hidden weight and Clamp temporarily holds an arm steady. I consider these effects when designing both levels and the economy.

Earning and spending

Booster What it does Price Unlocks at
Undo Brings back the pieces that just slipped. 40 brass Level 8
Clamp Keeps one arm from slipping for 3 placements. 80 brass Level 8
Scale Shows the weight of a hidden piece. 60 brass Level 37 (hidden pieces arrive with Glass)

The first completion awards 20 brass, plus 10 for a slip-free finish and 5 for using no boosters. Completing an exhibition for the first time adds 150. Replaying does not award these currency rewards again. New players start with 3 Undos, 3 Clamps and 2 Scales, available at their unlock levels.

Slip-free and booster-free are separate conditions. A player using Clamp without slipping can earn the extra 10, but not the extra 5. Using Undo after a slip earns neither. Gallery lights record a slip-free finish, rather than unassisted mastery.

Purchases and ads

PackContentsReference priceBrass per $1
Studio Starter5 Clamps, 5 Undos, 3 Scales and 300 brass · one purchase$2.99—
Small brass pack400$1.99≈ 201
Medium brass pack1,100$4.99≈ 220
Large brass pack · best unit value2,500$9.99≈ 250

These are reference prices in the project and have not been validated against payment behaviour. The starter pack appears from the first launch. Since Scale unlocks on level 37, I am reviewing the value of that part of the offer for a new player.

  • Rewarded ads: Players may choose to watch for one Scale, up to five times per day. There are no forced ads.
  • Review prompt: After completing an exhibition, at most three times and at least 30 days apart.
  • Purchase handling: Transaction IDs prevent duplicate grants, with a restoration flow in place.

The review prompt rule

ReviewRules.cs · when the review prompt appears
public static bool ShouldAsk(PlayerProfile profile, LevelReward reward, DateTime utcNow)
{
    if (profile == null || reward == null || reward.ExhibitionBonus <= 0) return false;
    if (!Tuning.ReviewPromptEnabled || profile.ReviewPrompts >= MaxPrompts) return false;
    return profile.ReviewPromptUtc == null || (utcNow - profile.ReviewPromptUtc.Value).TotalDays >= MinDaysBetween;
}

An economy risk in the simulation

The 4,000 sequential bots earn rewards and spend on Undo. They make no purchases and use neither Clamp nor Scale. This table describes that scenario. It is neither a lower bound on real spending nor a measure of human play time.

End of exhibitionMedian brassMedian time
Level 12 · Paper5708 min
Level 24 · Wood1,12521 min
Level 36 · Copper1,69531 min
Level 48 · Glass2,26039 min
Level 60 · Felt2,83551 min
Level 72 · Stone3,39060 min
Level 84 · Neon3,95071 min

Medians are calculated among bots that finished the relevant exhibition, rather than the entire starting cohort. They used Undo 6,451 times, with 1,170 paid from brass. The median finishing wallet was 3,950, enough for about 98 Undos.

Brass accumulates quickly in this scenario. I want to check whether rewards are too generous for the modelled Undo demand, then vary booster use and starting stock. The bots make no real-money purchases, so this does not estimate pack demand or revenue.

Technical details

The game, editor, solver and bots share the same rule code. A balance calculation therefore gives the same result in each. I set up the code this way to make changes easier to check.

Balance rules and solver limits

Instead of a physics engine, I use integer torque. A sub-arm contributes its total weight to the parent. Wind adds fixed torque. A slip requires exceeding the threshold and making the previous state worse, so helpful moves on initially tilted levels remain safe.

TorqueEngine.cs · torque and slipping
public static int Torque(Arm arm)
{
    int t = arm.Wind;
    foreach (var kv in arm.Slots)
    {
        var s = kv.Value;
        int w = s.IsPiece ? s.Piece.Weight : TotalWeight(s.Arm);
        t += w * kv.Key;
    }
    return t;
}

public static bool WouldSlip(int threshold, int torqueBefore, int torqueAfter)
{
    if (threshold <= 0) return false;
    int before = System.Math.Abs(torqueBefore);
    int after = System.Math.Abs(torqueAfter);
    return after > threshold && after > before;
}

The solver places heavier pieces first and prunes branches that remaining weights cannot balance. Above 5,040 orders per solution or 400,000 overall, it samples 200 orders per solution. Above 200 solutions, it selects 200 for estimating R. Sampling is reported. An exhausted placement search rejects the candidate.

Solver.cs · cutting hopeless branches early
void PieceDfs(List<Arm> arms, Dictionary<Arm, int> lever, int i, int placed)
{
    if (result.Solutions.Count >= maxSolutions || ++nodes > DfsNodeBudget) { truncated = true; return; }
    if (placed + (n - i) < target) return;
    foreach (var a in arms)
    {
        int t = Math.Abs(TorqueEngine.Torque(a));
        if (t > suffixWeight[i] * lever[a]) return;
    }

Automated checks and frame rate

The Unity-independent rule library has 139 automated tests covering rules, solver, level files, economy, shop, remote settings and translations. Bot tests can run without opening Unity.

After 1.5 seconds without activity, the game runs at 30 updates per second and renders every third update, about 10 renders per second. Touch restores full speed. Low memory or two consecutive slow performance windows can select 30 fps mode. The aim is to reduce unnecessary work. I have not measured a battery-life improvement.

FramePacing.cs · the starting frame rate and the slowness check
public static int Initial(string remoteMode, string saved, int ramMb)
{
    if (remoteMode == ModeSmooth) return Smooth;
    if (remoteMode == ModeSaver) return Saver;
    if (saved == ModeSaver) return Saver;
    return ramMb > 0 && ramMb < LowMemoryMb ? Saver : Smooth;
}

public static bool TooSlow(int target, FrameStats window) =>
    target > Saver && window != null && window.TotalSeconds >= WindowSeconds &&
    (window.AverageFps < target * MinAverageShare || window.SlowShare > MaxSlowShare);

Interface and visuals

I kept weight and distance visible in the interface. Weights are printed on pieces. Hidden pieces have a “?” and dashed outline, while locked hooks have a lock symbol. A red arm and a brief visual response show slip risk. I still want to check how clearly players read these cues.

I modelled the brass coin in Blender and designed the interface and icons in Figma.

Paper, ink and five colours

I used flat colours, no outlines, a light paper grain and soft shadows from the top left. Strings, text and hooks are ink black on cream paper. Pieces come in red, cobalt, yellow, black and white. I drew the shape images in white and let the game colour them. One image set can then cover six shapes in five colours.

  • Red circle, weight 3Circle
  • Cobalt leaf, weight 2Leaf
  • Yellow triangle, weight 2Triangle
  • Black diamond, weight 2Diamond
  • White drop, weight 1Drop
  • Cobalt star, weight 4Star
The six shapes in the game’s colours. The piece and shadow images are the game’s files. I added the numbers with the game’s placement and colour rules.
  • Paper #F3EDE2
    Background
  • Ink #1C1B1A
    String, text, black piece
  • Brass #B8944A
    Arm, coin
  • Red #D7301F
    Piece, slip warning
  • Cobalt #1F4FA3
    Piece
  • Yellow #E9B824
    Piece
  • White #FAF7F0
    Piece

Where does the weight number go?

Players read the weight on every move, but the image centre does not suit every shape. The triangle is widest at the bottom, and the drop’s body sits low. I calculated the largest circle inside each shape and placed the number at its centre. The number grows with weight but stays inside that circle. It is white on coloured pieces and ink on white ones. Yellow uses full white, where the contrast is lowest.

Triangle: dashed circle and number in the lower part, red cross higher up
Drop: dashed circle and number in the round body below, red cross higher up
The dashed circle is the largest circle that fits inside the shape. The number goes at its centre. The red cross is the centre of the shape’s bounding box.

The first set also had a crescent. Its thin body couldn’t hold the number, which spilled over the edge. I tried a half circle, a bean, a diamond and a hexagon side by side and chose the diamond: it hangs from its top corner and the number sits right in its middle.

  • Black crescent. The number 2 spills over its edgeCrescentThe number doesn’t fit
  • Two half-circle piecesHalf circle
  • Two bean-shaped piecesBean
  • Two diamond-shaped piecesDiamondChosen
  • Two hexagon piecesHexagon
The crescent and the four shapes I tried in its place.

Seven rooms, seven materials

The arm material changes with each exhibition: brass, wood, copper, glass, felt, stone and neon. Wood has wavy grain, stone has speckles, glass has a bright line across the top and neon has a white core. I used 9-slice sprites so only the middle stretches. The rounded ends keep their shape. Each room’s paper background also takes a slight tint from its material.

  • Paper
  • Wood
  • Copper
  • Glass
  • Felt
  • Stone
  • Neon

Icons, the brass coin and the app icon

I designed the icons in Figma on a 24-unit grid with 2-unit strokes. The same icon can be white on a dark button or ink on a light background. I modelled the brass coin in Blender. For the app icon, I used a brass arm with a red and a blue piece.

  • Scale
  • Clamp
  • Undo
  • Gallery
  • Shop
  • Settings
  • Locked
  • Ad
  • Lowest
  • Side by side
  • Apart
  • Arm
The first three are the boosters’ icons. The last four belong to rule cards.
  • Brass coin with a balance scale on itBrass coinBlender
  • A brass arm with a red and a blue circle on a cream backgroundApp icon

Gallery and catalogue

Next steps

I need to recruit real players for playtests to observe how they learn the relationship between weight and distance, discover moving and removing pieces, and react to the first slip.

Mozaik Blast

Overview

In Mozaik Blast, you clear groups of matching tiles to complete a ceramic pattern. Clearing the board is not the goal: a target cell is painted when the colour you clear matches its required colour. Finding the largest group is only part of the decision. Its position and the board it leaves behind matter too.

I took inspiration from İznik ceramics. I wanted each level to reveal a motif as you clear groups. The pattern gives you a visible way to follow your progress.

My work on the project

  • Level design: I designed all 100 levels across four chapters using the level editor I built in Unity, working on patterns, colour pools, obstacles and move budgets together. I planned introductions to new rules and easier levels after difficult ones.
  • Testing and improvements: I built the level editor and bot simulation, and reviewed the report for all 100 levels. I used the results to revise Servi and Kelebek. I compared Kilim’s move budget and two rules in bot A/B tests. I also ran bots through the game in order and analysed the gold economy and continues with SQL and Excel.
  • Product design: I developed the tutorial flow, lives and gold-leaf economy, continue offers and analytics events. I defined limits for remote settings and when updates take effect.
  • Implementation and visual design: I implemented the rules in Unity using C#, and made tile models in Blender. Each colour also has a distinct flower symbol to help distinguish tiles.

How I build a level

I start with the decision I want the player to make and where the level belongs in the chapter. Then I set its pattern, obstacles, colours and move budget in my editor. I play the draft, test it with bots and make changes. Before saving, I check the lesson and the neighbouring levels. Completion rate is one part of that decision.

Where the results come from

I could not access large-scale player testing, so I used bots with different ways of choosing moves. I report their assumptions with the results and compare designs under the same conditions. These are bot measurements. I have not yet checked their findings with real players.

ScopeTools
100 levels · 4 chapters · Android and iOSUnity 6 · C# · Figma · Blender
Level editor, bot simulation and CSV reportsSeparate Unity editor · reproducible tests
Analytics, remote settings, ads and purchase systemsFirebase Analytics · Remote Config · AdMob · Unity IAP
Analysing the bot dataSQL · Excel

Gameplay

Kilim’s 9×9 board, 33 moves and target pattern above
Level 25 · Kilim. The target pattern, move budget and board are visible together.

What happens in one move?

  1. Choose a group: Tap at least two matching tiles connected horizontally or vertically. Diagonal neighbours do not count, and invalid taps do not spend a move.
  2. Paint the pattern: A cleared tile paints its cell if its colour matches the target there. Targets have borders and hatching on the board, as well as a small pattern preview above it.
  3. Read the new board: Tiles fall and new ones fill the gaps. Large groups can leave a special tool at the tapped position.
  4. Finish the pattern: Paint every target cell to complete the level. Running out of moves opens the choice to continue or end the attempt.

Completed target cells are protected in all 100 current levels. Later blasts of the wrong colour do not erase that progress, so the player can focus on the remaining targets and preparing tools.

What large groups create

GroupToolThe decision it creates
7–9Brush: Recolours ordinary tiles in a row or column to its own colour. The group’s shape determines the direction.Paint a small target now, or change a line to prepare a larger group?
10–12Bucket: Paints up to 8 unpainted target cells correctly, prioritising its own colour and nearby targets.Save a tool for targets that are difficult to finish individually.
13+Seal: Paints targets correctly within a diamond extending two grid steps from its centre.Where should I tap the group to place the tool near more targets?

Adjacent tools can combine: two Brushes change both a row and a column, two Buckets increase capacity to 24 cells, and two Seals extend the radius to four grid steps. Bringing tools together adds another planning option alongside building large groups.

Game loop and onboarding

On the map, you choose the next pattern, see its preview and move budget, then play the board. A win completes the motif, awards gold leaf and unlocks the next level. The map brings completed mosaics and chapter progress together.

Within each attempt, the loop is read the remaining targets → choose between painting now and preparing a tool → clear a group → check the new layout. New tiles change the problem on each attempt. I keep early levels simple, then add obstacles and tighter budgets that require planning several moves ahead.

The first experience

I highlight the first map node and limit the first tap to a group that paints a target. The goal card follows that move. Tool tips appear when you first create each tool. Obstacle tips appear in their first level. I spread the explanations across the game instead of putting every rule on the opening screen.

The flow exists in the game. Bot completion does not establish that players understand it. Bots bypass the tutorial and already know the rules.

Tutorial highlighting a group for the first move
First move: a group that paints a target is highlighted.
Tutorial card explaining the target pattern
Next step: the pattern to complete is explained.

Level design

I give each level a purpose: teach a rule, combine earlier ones, encourage tool planning or offer a breather. I then arrange the targets and settings around it. Nearby targets, extra colours, tool reach, obstacles and moves all affect the choices. Scattered targets can make a Bucket valuable. A dense centre gives the Seal more to reach.

The 100 levels are divided into four chapters. Each opens by introducing a new situation with more room to learn. Later levels combine that rule with other demands.

ChapterLevelsDesign focus
Tile Workshop1–25Reading patterns and preparing tools. Three colours for the first 8 levels, then four. Kilim closes the chapter by combining the core rules before obstacles arrive.
Excavation26–50Single- and double-layer clay. First open access, then paint the target underneath.
Palace51–75Cages block falling. Clearing a group also becomes a way to reopen flow. Later levels combine cages and clay.
Garden76–100Moss spreads when ignored. Prioritising between painting targets, opening space and stopping growth.

From the plan to the board

Level and roleDecision I designed forWhat I checked
20 · Servi — breatherComplete the remaining targets without adding a new rule.The bottom target in losses, available moves and the level’s place after a harder one.
25 · Kilim — chapter finalePrepare tools and choose where they will reach the pattern.Seal position and the completion rates at 31, 33 and 35 moves.
97 · Kelebek — combining demandsBalance painting targets with controlling moss.Whether the moss lesson survives the change, alongside the gap between bot profiles.

The target pattern and the playing tiles

The level file holds the target pattern, obstacles, colours and move budget. The game fills and replenishes ordinary tiles, so one designed level can produce many boards. I test it across repeated runs. Seeds help me repeat the measurement, with limits that I explain for each test.

How do obstacles change the decision?

  • Clay: A covered cell cannot be painted. Clearing an adjacent group removes one layer. Double-layer clay needs two hits. Removing clay does not complete the target underneath: it still needs the correct colour.
  • Cage: The tile inside joins matching groups, but the first blast breaks only the cage and leaves the tile in place. A cage also blocks falling until it breaks, so a move can open a route for the next move rather than paint a target immediately.
  • Moss: It covers cells. If no moss is removed during a continuing move, it spreads onto a neighbouring ordinary tile. Removing moss stops growth on that move. The player must prioritise between painting targets and controlling spread.
Kilim board
25 · Kilim Tools and pattern layout.
Mihrap board with clay
33 · Mihrap Two clay layers in the centre.
Kilit board with cages
51 · Kilit Introducing cages.
Board with moss
76 · Yosun Introducing a spreading obstacle.

Difficulty rhythm

I wanted changes in pressure rather than a continuous climb. The variable-choice bot completes Dalga (7) at 41.6%, followed by Güneş (8) at 84.2%. Mahzen (31, 43.1%) is followed by Fener (85.5%), and Kuru Havuz (81, 38.1%) by Lale Tarlası (85.6%). These easier levels provide a break after a demanding one.

The target below is the bot completion target stored in each level file. It is not a measured benchmark for players. A close match shows that the move budget meets the design target under this model.

  • Design target
  • Variable-choice bot

Completion rate · a higher value means an easier level

0%25%50%75%100%Tile Workshop1–25Excavation26–50Palace51–75Garden76–100
3 October 2026 verification · 1,000 runs per profile per level · 100 levels. The chart and value table show that version. Later Servi and Kelebek edits appear in Changes based on bot tests.
Show the values for all 100 levels
LevelMovesTargetVariable-choiceGoal-focusedRandom
1 · İlk Çini992%94.5%96.8%21.5%
2 · Çizgi992%93.0%94.9%24.3%
3 · Köşe1090%92.8%94.3%21.9%
4 · Elmas1090%89.1%93.3%17.2%
5 · Nazar1586%87.5%91.4%5.7%
6 · Yaprak1180%77.4%84.2%2.8%
7 · Dalga838%41.6%41.7%1.7%
8 · Güneş1386%84.2%88.1%5.7%
9 · Lale2178%74.5%83.4%7.7%
10 · Yıldız2177%76.1%81.1%2.8%
11 · Hilal1576%74.5%77.6%11.2%
12 · Çerçeve2174%77.9%80.9%9.6%
13 · Kum Saati1140%40.6%40.6%1.0%
14 · Nar2786%85.0%88.8%7.1%
15 · Çintemani2374%73.5%74.8%5.6%
16 · Kapı1673%75.0%76.9%7.5%
17 · Rumi1872%73.1%75.7%6.5%
18 · Merdiven1772%71.4%73.0%9.0%
19 · İki Lale1740%43.7%44.3%0.6%
20 · Servi2786%86.5%89.1%10.8%
21 · Karanfil3072%74.0%78.2%2.0%
22 · Kandil2470%71.2%77.7%1.4%
23 · Zencerek3070%71.0%74.7%2.1%
24 · Hatai2868%70.4%75.1%1.1%
25 · Kilim3360%60.2%66.8%0.2%
26 · Kazı2488%90.7%92.7%7.8%
27 · Kuyu3282%80.1%84.5%5.5%
28 · Sandık2878%80.5%82.8%4.4%
29 · Kale3376%74.6%81.0%6.9%
30 · Çift Kat2976%78.3%82.9%3.6%
31 · Mahzen2640%43.1%47.3%0.5%
32 · Fener2986%85.5%88.0%8.3%
33 · Mihrap3372%69.7%75.6%2.3%
34 · Çeşme3072%71.5%78.3%1.4%
35 · Hamam3570%70.1%77.4%1.7%
36 · Köprü2870%71.8%74.6%2.2%
37 · Labirent2340%41.5%44.4%2.5%
38 · Gül Bahçesi2986%85.0%87.0%9.0%
39 · Tavus3070%71.9%79.0%1.3%
40 · Tespih2268%69.0%71.6%2.4%
41 · Sebil3368%66.7%72.6%1.5%
42 · Kervansaray3166%63.6%70.0%1.2%
43 · Zindan2240%44.4%48.2%0.2%
44 · Nilüfer3486%85.7%89.3%10.1%
45 · Minare3566%67.2%73.0%0.5%
46 · Kubbe3864%64.9%72.1%0.2%
47 · Saray3562%62.2%67.8%0.3%
48 · Pusula2762%59.9%69.3%1.4%
49 · Hazine Odası3160%61.0%65.6%0.4%
50 · Ayasofya4056%55.2%60.7%0.4%
51 · Kilit3388%89.7%90.3%9.9%
52 · Parmaklık3086%86.6%90.3%22.9%
53 · Taş Kapı2474%77.7%82.4%8.7%
54 · Taht3672%75.3%79.4%2.8%
55 · Harem Penceresi2170%69.4%71.5%3.8%
56 · Zincir1540%42.0%47.8%0.9%
57 · Gül2984%83.7%86.3%8.7%
58 · Topkapı Kapısı3370%68.1%73.9%1.6%
59 · Sofa2468%65.0%71.1%3.1%
60 · Kaftan3168%69.3%73.1%1.3%
61 · Tuğra2166%65.0%69.3%2.7%
62 · Hazine Dairesi2238%41.6%44.7%0.2%
63 · Lale Bahçesi3384%85.7%87.8%8.5%
64 · Çini Paneli2966%65.4%73.4%3.1%
65 · Fıskiye2564%61.5%67.8%1.3%
66 · Divan3462%66.3%70.7%0.6%
67 · Kütüphane2962%64.3%66.1%0.7%
68 · Zindan Kapısı1838%40.2%42.1%0.6%
69 · Havuz3184%83.8%84.8%7.1%
70 · Hünkâr Kasrı3760%62.0%67.9%0.5%
71 · Kemer2558%57.9%62.8%1.9%
72 · Nişan3358%60.5%64.9%1.4%
73 · Mozaik Zemin3156%59.9%63.4%1.9%
74 · Alem2056%55.9%61.9%1.2%
75 · Topkapı Sarayı4052%51.7%56.2%0.2%
76 · Yosun2588%88.2%90.0%6.8%
77 · Sarmaşık2586%86.8%88.5%15.3%
78 · Çiçek Tarhı2774%75.6%78.4%2.0%
79 · Fıskiyeli Havuz2972%72.4%74.3%1.2%
80 · Taş Yol1970%68.0%73.2%2.5%
81 · Kuru Havuz1138%38.1%40.4%1.9%
82 · Lale Tarlası3184%85.6%87.9%4.2%
83 · Çardak3068%67.6%73.0%1.3%
84 · Kameriye2566%68.2%71.7%0.4%
85 · Sera2866%66.0%69.6%0.9%
86 · Bostan2864%65.8%69.6%0.4%
87 · Çit Labirenti1638%41.0%40.8%0.3%
88 · Gülistan2684%82.9%86.2%7.3%
89 · Çeşme Başı2362%61.7%67.9%0.3%
90 · Kuş Evi3160%60.8%67.3%0.0%
91 · Asma2860%61.3%67.7%0.1%
92 · Nar Ağacı3158%59.5%66.4%0.2%
93 · Harabe1538%38.6%45.8%0.3%
94 · Çimen2684%82.6%87.6%7.9%
95 · Kuyu Başı2456%56.0%62.4%0.7%
96 · Ağaç Evi3556%56.7%63.4%0.0%
97 · Kelebek3054%50.0%62.4%0.1%
98 · Bahçıvan Kulübesi3554%57.4%61.2%0.2%
99 · Gizli Bahçe3852%52.6%55.5%0.5%
100 · Cennet Bahçesi3950%50.3%55.9%0.0%

Level editor and workflow

I designed all 100 levels in the editor I built in Unity. I use each level’s role and lesson to decide what to change. The library sits on the left, the board in the middle, and the settings and plan on the right. I check a draft with bots, open it in the game without saving and save the chosen design with its report.

The plan is read from a separate design table and does not update automatically when I edit the board. Servi’s older plan note still describes 22 targets. The current board and saved design have 24. I use the current board counter and saved report when checking the final version.

Mozaik Blast’s separate Unity level editor: Servi’s target pattern, level library, design plan, settings and bot results
Level 20, Servi’s current pattern. The library is on the left, target and obstacle layers in the middle, and the design plan and settings on the right, with bot results below. Click the image for a larger view.

How I edit a level

  1. Start with the level’s purpose. I open Mozaik Blast → Level Editor in Unity and choose a level from the library. Its role, difficulty lever and intended lesson from the design table are visible alongside it. Before editing, I run the bots and keep the result with “Set reference”.
  2. Edit the pattern and obstacles separately. I paint targets with colour brushes, and clay, double clay, cages and moss on the obstacle layer. Erasing one layer preserves the other. A drag can be undone in one step.
  3. Adjust and measure again. Board size, colours, moves and painted-cell protection are in one panel. The default test runs 1,000 games per profile. If the reference has a different run count or seed, the editor hides the difference column. I also keep the game rules fixed. The move suggestion searches with 300-run tests, then runs the full test. I review the suggestion before choosing it.
  4. Look at the cells behind the rate. The difficulty heatmap shows targets left unpainted in games lost by the variable-choice bot. Percentages use only lost games as their denominator. Colour intensity scales to the hottest value on that map, so I do not compare colour darkness directly across levels.
  5. Try the draft in the game. “Playtest” opens the game scene without changing the level file. I check the goal preview, pattern readability and tool behaviour there. Leaving Play Mode restores the previous lives, gold and progress.
  6. Save the design and report. Saving reruns the bots if the measurement is out of date. The file keeps the run count, seed, rules, date and three profile results. A design or rule change marks the report as stale. I can export levels as JSON and measurements as CSV. Library markers show which reports passed, need attention or are stale.

The simple checks behind my decisions

I read the bot results with five simple checks. The editor calculates all of them. The examples come from Servi’s and Kelebek’s real measurements.

Is it close to the target?

Variable-choice bot − target = within ±5 points

Kelebek’s target is 54%. After the edit, the variable-choice bot wins 56.5%: 2.5 points away.

Can luck beat it?

A bot tapping at random wins at most 15%

In Kelebek the random bot wins only 1 game in 1,000. I do not apply this limit to easy introductory levels with a target of 85% or more.

Is skill rewarded?

Goal-focused bot − variable-choice bot = 3–8 points

Kelebek was 62.4 − 50.0 = 12.4 points before, so mistakes were punished hard. Afterwards it was 63.0 − 56.5 = 6.5 points.

Where do players get stuck?

Lost games where the cell stayed empty ÷ all lost games

In Servi the bottom trunk cell stayed empty in 44 of every 100 losses. After I added the root, the hardest cell dropped to 30 in 100.

Which change worked?

Effect = new result − previous result, one change at a time

Moving Kelebek’s moss and adding a move improved the variable-choice bot by 6.4 points. Adding only the move improved it by 6.5. The extra move was enough in this measurement, so I kept the moss where it was.

How I read the bot results

Servi’s bot results: variable-choice 88.9 percent, goal-focused 91.2 percent, random 11.4 percent. Remaining moves and progress on losses in separate columns
Servi’s current measurement. Remaining moves average only wins. Loss progress averages only losses. Tool creation and use are reported separately too.

The editor’s “ready to ship” label means the level passed my bot checks. The variable-choice bot must be within 5 percentage points of its target. Below an 85% target, the random bot must not exceed 15%. The label does not confirm how difficult or enjoyable a level is for people.

I also watch the difference between the goal-focused and variable-choice profiles. “Skill gap” here means only the difference between those two bots’ completion rates. I use 3–8 points as a review range. Falling outside it does not block saving by itself. Easy levels with a target of at least 85% also receive no warning for a gap below 3 points.

I used this workflow to revise Servi’s target layout and Kelebek’s move budget. The problems, alternatives and before-and-after results appear in Changes based on bot tests.

A limit the editor showed me

I noticed that heatmaps of symmetric levels were consistently hotter on the right. In level 73, Mozaik Zemin, the caged bottom-left target stays empty in 16% of losses, compared with 50% on the right. Bots always tap the leftmost cell in a group, where the tool then appears. That behaviour may affect the difference. I do not treat the hotter side as proof of a layout problem. The editor shows this limitation below the map. I want to test different tap positions next.

Why change each setting?

Editor settingDesign question
Target layoutCan a tool reach several targets together, or must they be pursued separately? Do final targets sit in the centre or along the edges?
Obstacle type and layersDoes a move paint, open access or stop spreading? Am I introducing a rule alongside too many other demands?
Board dimensions and colour poolHow much space is available for groups and tools? Do extra pool colours add tiles outside the motif? A larger board is not automatically harder.
Move budget and bot targetDoes the budget support the level’s place in the chapter? How do remaining moves and progress on losses change alongside completion?
Protecting painted cellsDoes earned progress persist? Changing this rule affects decisions as well as difficulty. Kilim’s separate bot comparison appears below.

Looking across all levels

The balancing menu can check a chapter, selected levels or all 100. Budget search, fine-tuning and verification without changing moves are separate actions. The chart’s 3 October report uses verification and covers the levels as they were then. It excludes the later Servi and Kelebek edits. The CSV contains targets, old and new budgets, three completion rates, and the variable-choice bot’s remaining moves, loss progress, group sizes and tool use. I use it to check neighbouring levels together.

What I check before saving

  • Pattern and access: Do target colours and the colour pool agree? Did resizing remove a target or obstacle? Are covered targets and the preview readable?
  • Teaching and rhythm: Which decision does the level emphasise? Do obstacles, tool setup space and move budget support it? How does pressure change from the previous level to the next?
  • Fresh measurements: Did I rerun the bots after the last edit? Are run count, seed and rules suitable for the comparison? Am I reading win and loss averages separately?

The editor limits board size to 4–10 and moves to 1–99. It refuses target-free levels and will not overwrite a file changed elsewhere. It warns about missing target colours and targets outside the planned role’s range. I check the lesson and readability by playing the draft.

Level 25: Kilim

Kilim closes the first chapter: a 9×9 board, four colours, 35 target cells, 33 moves and no obstacles. Seven horizontal colour bands make the group’s location part of the decision after the tools have been introduced.

The same tool in a different position

A Seal does not paint the entire board. Its two-step diamond reaches 13 target cells at the centre of this pattern, versus 9 at the left edge. The diagram assumes all targets are still unpainted. Actual gains depend on the cells remaining at that point.

Centre: 13 target cells
Edge: 9 target cells

The tool appears at the cell tapped within the large group. “Find a group of 13” therefore becomes “where should I tap it?” Colour bands give the Brush a way to prepare large groups, while the dense centre makes Seal placement valuable.

I compared Kilim’s 33-move budget with 31 and 35 moves. The rates, test settings and decision to keep 33 moves appear in Bot A/B tests.

Tests and balancing

I use three bots that choose moves differently. They all use the game’s board model and rules, and I report them separately. This test does not play animations, tutorials or the economy flow.

ProfileBehavioural assumptionLimitation
Goal-focusedPreviews the listed move candidates. Scores target gains, obstacle damage, tool creation and opportunities for the next group, then chooses the highest score.Knows the rules and full board state. Neither an exhaustive solver nor a learning player.
Variable-choiceUses the same scoring. On 25% of choices it selects randomly among the top three moves, including the best move.This is not a human who makes mistakes 25% of the time. Its source name is Human. The human CSV column is bot data.
RandomChooses uniformly among the listed legal move candidates.Does not tap every screen cell at random. Not a validated model of an impatient or new player.

All three profiles represent an ordinary group with one tap position rather than comparing every possible tool placement within it. The 13-versus-9-cell example in Kilim was therefore examined geometrically. Bot results do not fully represent the skill of placing a tool.

Checking all 100 levels

The 3 October check ran 1,000 games per level and profile: 100 × 3 × 1,000 = 300,000 runs. Seed: 1234. Move budgets stayed unchanged during verification. I later changed Servi from 27 to 31 moves and 22 to 24 targets, and Kelebek from 30 to 31 moves. Those designs and measurements appear separately in Changes based on bot tests.

In that report, the variable-choice bot is within 4.4 percentage points of the target on every level. The intended rhythm is possible under this model. I have not measured players’ difficulty, enjoyment or return behaviour. The report’s processing time is also not player time.

Reproducibility and quality checks

The new Kilim measurement ran with unchanged copies of the game source. I first compared 150 runs per profile on levels 1, 7, 25, 26, 51, 76 and 100 with the existing Unity regression record. All 21 win-count comparisons matched. I then measured the alternatives.

The project also contains a QA record of 131 passing tests covering areas such as board rules, obstacles, tools, level loading and the economy. Technical checks and bot balancing serve different purposes: a rule working correctly does not show that a level feels good to play.

Win counts checked against Unity

At seed 777, each cell shows wins out of 150 runs. All 21 counts matched the Unity record. These 3,150 checks are separate from the 16,200 Kilim experiment runs.

LevelVariable-choiceGoal-focusedRandom
1144 / 150143 / 15034 / 150
762 / 15055 / 1501 / 150
2591 / 15097 / 1500 / 150
26134 / 150140 / 15015 / 150
51136 / 150130 / 15010 / 150
76125 / 150128 / 15012 / 150
10067 / 15083 / 1500 / 150

Changes based on bot tests

In Servi, I found a target that often stayed empty in losses. In Kelebek, I found a wide gap between two bots. I tested changes to each while keeping the level’s purpose. I played the drafts and saved my chosen designs.

LevelSaved changeVariable-choice bot · before → afterDesign decision
20 · ServiAdded two coral targets to the root. Raised the budget from 27 to 31 moves.86.5% → 88.9%Preserve the breather role. Revise the bottom target often left incomplete in losses.
97 · KelebekKept the moss position. Raised the budget from 30 to 31 moves.50.0% → 56.5%Keep the moss lesson. Choose the budget that reduced the profile gap from 12.4 to 6.5 points.

These are design edits measured with bots. Their effects on real-player completion, enjoyment or return behaviour have not yet been established.

20 · Servi: a breather level’s final target

Problem

Servi follows a hard level and has an 86% bot target. Its result was close, but the heatmap drew my attention to the bottom of the trunk. That cell stayed empty in 44% of the variable-choice bot’s 135 losses. Losing bots had painted an average of 92% of the targets. Most of the pattern was complete, yet the same cell often remained. I revisited the layout because this was meant to be a breather.

Change

I added two coral cells under the trunk to make a three-cell root. The bottom target is no longer on its own. A side-by-side group can paint it. The move suggestion raised the budget from 27 to 31. I opened the draft in the game, checked that the root reads clearly in the goal preview, and saved it.

Servi’s earlier heatmap: the bottom trunk cell is the hottest at 44 percent
Before27 moves, 22 target cells. Hottest cell 44%.
Servi’s new heatmap: a three-cell root was added and the hottest cell is 30 percent
After31 moves, 24 target cells. Hottest cell 30%.

Result and decision

MeasureBeforeAfter
Moves · target cells27 · 2231 · 24
Variable-choice bot86.5%88.9%
Goal-focused bot89.1%91.2%
Random bot10.8%11.4%
Lost games (of 1,000)135111
Hottest cell44% · bottom of the trunk30% · right end of the root

The variable-choice bot is now 2.9 points above target. I accepted that margin for a breather. I changed both the root and the move budget, so I cannot attribute the improvement to the root alone. The hotter right end may also reflect the bot’s tap position.

97 · Kelebek: keep the lesson, change the budget

Problem

Kelebek’s bot target is 54%. The variable-choice bot stopped at 50.0%, while the goal-focused bot reached 62.4%. The 12.4-point gap was outside my 3–8-point review range for these two bot profiles. It does not establish how players feel when they make a mistake.

Alternatives I tried

I first moved the moss from the middle of the body to the top, where it could no longer spread straight into both wings. The result looked better, but the move suggestion had also raised the budget from 30 to 31. I put the moss back and tested 31 moves alone.

AttemptVariable-choiceGoal-focusedSkill gap
Earlier version · 30 moves50.0%62.4%12.4 points
Moss at the top of the body · 31 moves56.4%61.1%4.7 points
Only 31 moves · the version I saved56.5%63.0%6.5 points

Result and decision

The two 31-move options gave very similar variable-choice results. Changing only the budget met the target and profile-gap range. Moss spreading from the body is part of the lesson I want to keep, so I left it there and saved 31 moves. The bot is 2.5 points above target, with a 6.5-point gap. This does not prove the moss position has no effect.

Both examples use 1,000 runs per profile and seed 1234 with the same rules. In this editor test, using the same seed does not pair every run with an identical starting board: move choices and game length can change the random sequence. The comparisons show results under these bot settings. They are not matched-start player A/B tests. Heatmap percentages use lost games only.

Settings in the saved reports

Both reports use 1,000 runs per profile and seed 1234. Tile guidance is 30%. Tool thresholds are 7, 10 and 13, Bucket capacity is 8, and Seal radius is 2. Remaining moves are averaged over wins, and target progress over losses, for the variable-choice profile. These saved reports describe the selected designs. They do not contain the raw runs for every earlier alternative.

Saved design · 9 OctoberMoves / targetsMoves left in winsProgress in losses
20 · Servi · 22:5631 / 2412.8793.2%
97 · Kelebek · 22:5431 / 387.0491.4%

Bot A/B tests

I tested three questions on Kilim: how many moves to allow, whether painted cells should stay complete, and how much new tiles should help with targets. I changed one setting per comparison and kept the other rules fixed. The same bot profiles played the alternatives. No real players were assigned to test groups.

Hypotheses and test plan

My main measure was closeness to Kilim’s 60% completion target for the variable-choice bot. I also watched the goal-focused and random bots. Each condition has 900 runs per profile. Pattern, colours and tool rules are fixed. Only the setting named in that experiment changes.

1. Move budget: 31, 33 or 35?

Idea tested: 33 moves leave enough space for a chapter finale that requires tool planning. Fewer moves may put completion below target, and more moves may take it above target.

I retested the current 33-move version against 31 and 35 moves using the same scenario seeds. The pattern, colour pool, tools and tile-arrival setting stayed fixed. Each option has 900 runs per profile, or 2,700 across all three profiles.

MovesVariable-choiceGoal-focusedRandom
3154.6%61.3%0.1%
3362.1%70.1%0.1%
3571.3%76.9%0.4%

Decision: I kept 33 moves. The variable-choice bot completes 62.1%, close to the 60% target. It falls to 54.6% with 31 moves and rises to 71.3% with 35. I wanted the chapter finale to require tool planning, so I did not choose the highest completion rate.

At 33 moves, losing runs from this profile complete an average of 92.1% of the pattern. That flags the last few targets for closer inspection. It does not measure frustration or willingness to buy extra moves. Winning runs have an average of 7.38 moves left, a figure that applies only to wins.

This is a local comparison of alternatives. I do not present the existing report as a before-and-after improvement, and the game’s level file was not changed for the test.

2. Should completed cells stay protected?

Idea tested: Protecting correctly painted cells lets the bot focus on remaining targets and sustain progress. Removing protection may make completion harder with the same budget and move selection. I also measured target progress in lost games.

In the current version, a correctly painted cell stays complete. In the alternative, later blasts of the wrong colour can undo that progress. This adds a need to protect previous work alongside completing the remaining targets.

Painting ruleWinning runsCompletionTarget progress in losses
Protected cells · current559 / 90062.1%92.1%
Wrong colours can undo progress3 / 9000.3%63.0%

Decision: I kept painted cells protected. Without protection, variable-choice completion fell from 62.1% to 0.3%, and progress in losses from 92.1% to 63.0%. The alternative made Kilim much harder at this budget. I did not retune the bot or rebalance moves for it. This test does not establish that overwriting is a poor rule in every game.

3. How much should new tiles help with targets?

Idea tested: 30% guidance may keep completion close to the design target. Removing guidance or increasing it to 60% may change that balance. I examined random-profile completion alongside the variable-choice rate.

The current tile-arrival setting has 30% guidance: if a column has missing target colours, a new tile has a 30% chance of drawing from those colours. Otherwise it uses the ordinary colour pool. This is neither a 30% win boost nor a guarantee of a useful group.

Tile-arrival settingWinning runsCompletionRandom profile
No guidance · 0%460 / 90051.1%0.0%
Current · 30%559 / 90062.1%0.1%
Stronger · 60%660 / 90073.3%0.4%

Decision: I kept 30% guidance on Kilim. Without it, variable-choice completion is 51.1%. At 60% guidance it reaches 73.3%. The current setting is close to my 60% target, while the random bot completes 0.1%. These results help me choose a setting for this level. They do not measure a player’s sense of fairness or control.

Experiment scope

There are six conditions: 31, 33 and 35 moves. Overwriting at 33 moves, and 0% and 60% tile guidance at 33 moves. Each has 900 runs per profile, 2,700 in total, giving 16,200 unique runs. The current version is shared across comparisons and is not counted again.

I used three seed blocks of 300 runs. The move-budget and painting comparisons share starting scenarios. Changing tile arrival means the same seed does not guarantee the same board. These are bot A/B comparisons, not a live experiment splitting players into two groups. Settings and sampling uncertainty are shown below.

All three profiles across six conditions

ConditionVariable-choiceGoal-focusedRandom
31 moves491 / 900 · 54.6%552 / 900 · 61.3%1 / 900 · 0.1%
33 moves · current559 / 900 · 62.1%631 / 900 · 70.1%1 / 900 · 0.1%
35 moves642 / 900 · 71.3%692 / 900 · 76.9%4 / 900 · 0.4%
Overwriting · 33 moves3 / 900 · 0.3%4 / 900 · 0.4%0 / 900 · 0.0%
0% guidance · 33 moves460 / 900 · 51.1%511 / 900 · 56.8%0 / 900 · 0.0%
60% guidance · 33 moves660 / 900 · 73.3%721 / 900 · 80.1%4 / 900 · 0.4%

Settings and uncertainty

The seed blocks are 1729, 8675309 and 20261009, with 300 runs in each. A run’s seed is block + trial × 104729, starting trial numbering at zero. Each game is a separate simulator call, so the previous game’s random-number use does not change the next starting scenario. Move and painting comparisons share starts. Tile-arrival changes and different profile choices can change the boards that follow.

All conditions use a 9×9 board, 35 targets and four colours. Tools are enabled, splash is disabled, thresholds are 7/10/13, Bucket capacity is 8 and Seal radius is 2. The current variable-choice result is 559/900 (62.1%), with a 95% Wilson interval of about 58.9–65.2%. This describes sampling uncertainty for the bot and scenarios, not uncertainty about real-player behaviour.

Remaining moves use wins only. Target progress uses losses only. Tool creation and use are averaged across all runs. The older 100-level report’s seconds measure computation time, not play time. Its Kilim rate of 60.2% and this experiment’s 62.1% use different seed methods, so their difference is not a design improvement.

SQL and Excel

To examine the gold-leaf economy and continue offers, I made a bot run that plays the levels in order from the start. It uses the game’s own board and bot code. On Servi it reproduced the editor’s saved result exactly.

  • 4,000 bots: 70% variable-choice and 30% goal-focused. A bot that loses the same level 3–8 times in a row quits.
  • When moves run out, a bot takes one continue for 30 gold if at least 80% of the pattern is painted and it can afford it. This is my assumption. Ads, life timers and purchases are not modelled.
  • 434,900 attempts were played in total. 2,910 of the 4,000 bots cleared level 100.

I put the data into three simple tables and asked questions with SQL. The queries run in SQLite.

  • players: one bot per row: its profile, how many levels it cleared, the level where it quit, and gold earned and spent.
  • attempts: one attempt per row: the level, whether it was won, whether a continue was taken, how much of the pattern was painted at the offer, and gold earned and spent.
  • levels: each level’s name, chapter and type.

1. Does a continue bought with gold save the attempt?

SQL · in-order play data
-- Altınla alınan continue attempt'i kurtarıyor mu?
SELECT CASE WHEN offer_progress >= 90 THEN '90-99%' ELSE '80-89%' END AS painted_at_offer,
       COUNT(*) AS continues,
       ROUND(100.0 * SUM(won) / COUNT(*), 1) AS won_pct
FROM attempts
WHERE continued = 1
GROUP BY painted_at_offer;
painted_at_offercontinueswon_pct
80-89%3791832.5
90-99%7254054.8

What the query does: It selects attempts with a continue: continued = 1. CASE WHEN puts an attempt in the “90-99%” group if at least 90% of the pattern was painted at the offer, otherwise in the “80-89%” group. For each group it counts the continues and calculates the share of attempts that were won.

What I found: Continues taken at 90% or more painted progress win 54.8% of the time. At 80–89%, the rate is 32.5%. The outcome of spending the same 30 gold depends on what remains. The offer already shows painted progress. This result supports my reason for keeping that information visible.

2. On which levels are we losing the most bots?

SQL · in-order play data
-- Botları en çok hangi level'larda kaybediyoruz?
SELECT l.level,
       l.name,
       l.tier,
       COUNT(*) AS bots_quit
FROM players p
JOIN levels l ON l.level = p.stopped_at_level
WHERE p.finished = 0
GROUP BY l.level, l.name, l.tier
ORDER BY bots_quit DESC
LIMIT 5;
levelnametierbots_quit
13Kum Saatihard99
37Labirenthard89
19İki Lalehard83
56Zincirhard81
81Kuru Havuzhard75

What the query does: It groups the bots that did not finish by the level where they quit. The JOIN brings each level’s name and type from the levels table. The five levels that lost the most bots are listed.

What I found: All five are hard levels I planned: Kum Saati, Labirent, İki Lale, Zincir and Kuru Havuz. The numbers are small because in this model bots quit only after several losses in a row and can take continues. Real player loss has other causes too. Here the result shows that difficulty gathers where I planned it.

3. How much of the gold earned goes to continues?

SQL · in-order play data
-- Her temada kazanılan altının ne kadarı continue'a gidiyor?
SELECT l.theme,
       SUM(a.gold_earned) AS gold_earned,
       SUM(a.gold_spent) AS gold_spent,
       ROUND(100.0 * SUM(a.gold_spent) / SUM(a.gold_earned), 1) AS spent_pct
FROM attempts a
JOIN levels l ON l.level = a.level
GROUP BY l.theme_no, l.theme
ORDER BY l.theme_no;
themegold_earnedgold_spentspent_pct
Tile Workshop109446166939061.2
Excavation113233789778079.3
Palace104003588323084.9
Garden90186986334095.7

What the query does: It joins the attempts table to the levels table, adds up the gold earned and spent on continues in each chapter, and divides spent by earned to give a percentage.

What I found: Continues use 61% of gold earned in Tile Workshop and 96% in Garden. Little gold accumulates towards the end in this scenario. The bots always continue when the conditions allow it. The result depends on that choice and is not a purchase or revenue forecast.

Excel workbook

I also prepared the same run in Excel. Each bot is a row on the Players sheet, and each continue with its result is a row on the Continues sheet. The Continue sheet shows the result separately for variable-choice and goal-focused bots. The Levels sheet shows how many bots start and quit each level, and the Economy sheet shows gold by chapter. Everything is calculated with COUNTIFS and SUM.

Excel · two formulas
Continue!B2 · continues taken with 80–89% of the pattern painted
=COUNTIFS(Continues!E:E, ">=80", Continues!E:E, "<=89")
Levels!G2 · bots that quit the game on this level
=COUNTIFS(Players!E:E, A2)
The Continue sheet in Excel: continues and the win rate after a continue by how much of the pattern was painted, for all bots and both profiles, with a column chart on the right.
The Continue sheet in Excel. Both profiles point the same way. Select the image to enlarge it.

All tables in this section come from bot data, not from real players. The run was made on 12 October 2026 with seed 20261012.

Analytics and product decisions

The bots let me compare board designs. Product decisions also need evidence about how players move through tutorials, levels and offers. I added events for those steps and for currency activity. The table is a measurement and decision plan. It contains no observed player results yet.

QuestionExisting events and measurementDecision to assess when data arrives
Is the rule understood initially?tutorial_begin / step / complete · step drop-off and completionChange text or highlighting if players struggle. Event order alone cannot explain why. Check with a small observational test.
Which level creates too much friction?level_start / level_end · attempt count, win/fail/quit/restart, moves left and progressSeparate first attempts, replays and wins with extra moves. Test target layout if the last cells cause trouble, or move budget if pressure is more general.
What happens when moves run out?out_of_moves / continue_used · available offers, gold, progress and continue methodMeasure continuation among attempts eligible for the offer and their later outcomes. Low use alone does not establish that an option is overpriced or unhelpful.
Where does the economy stall?earn_virtual_currency / spend_virtual_currency / lives_refill · sources, sinks and life refillsTrack replay rewards separately and examine how currency balances relate to life and continue pressure.
Do shop and ad flows work?shop_open / shop_purchase / rewarded_ad · entry source, purchase outcome and reward deliveryDistinguish completed, cancelled, pending and failed purchases. Check delivery first, then form a conversion hypothesis.

level_end records the result, attempt count, duration, moves used and remaining, target progress, and gold/ad continue counts. out_of_moves records which continue offers were actually available. This helps avoid treating an unavailable offer as a rejected offer.

I have no player results for retention, revenue or purchase conversion yet. When data arrives, I want to check the events first, then form level-specific hypotheses. Remote Config can change Kilim’s move budget. Painting and tile-guidance tests would need a new gameplay-rule version.

Economy and monetisation

The currency is gold leaf. Level wins, rewarded ads and store packs supply it. Extra moves and life refills spend it. The values below are the game’s default settings, not prices established as optimal through player data.

SystemDefaultDesign consideration
Starting balance and winsStart with 60 · win reward: 5 + moves remainingEfficient completion is rewarded. Replayed wins also award currency, so accumulation and easy-level farming need monitoring.
LivesMaximum 5 · one regenerates every 30 minutesLives are part of the attempt-ending flow. Bot measurements exclude waiting and session effects.
Continue with gold+5 moves · gold continues cost 30, then 60, 90…Cost rises with gold continues in the same attempt. Ad continues do not increase this counter.
Life refill100 gold leafExamine balance and life depletion together, not only spend counts.
Rewarded adsContinue: +5 moves, at most once per attempt · free currency: 20, at most 5 times per dayExtra-life and double-win-reward options also exist. Reward delivery depends on ad completion.
Purchases300 / 1,000 / 2,500 gold-leaf packsPrices come from the store in local currency. Actual purchase or revenue results are outside this study.

I balance levels without extra moves or purchases. I do not want the economy to hide a problem in the base level. Remote Config can change prices and ad limits. Before a change, I need to name the question and the measures I want to protect.

Technical details

How the code is organised

I wrote the game in C# with Unity 6. The rules are separate from how they appear on screen. The editor and bots use the same rules. When I change one, the game and tools keep using the same code.

PartWhat it does
Rules (Core)Board, clearing, painting, obstacles, tools, level files and player progress. No visual or scene code.
GameHow the board looks, animation, interface, haptics and the flow between screens.
ServicesFirebase (analytics and remote settings), AdMob and Unity IAP. The game reaches them through interfaces. If a package is not installed, that code is not compiled.
Developer tools (Dev)Bots, in-game shortcuts, and test ads and store. They exist only in the Unity editor and development builds, not in the store version.
Editor and testsThe level editor, balancing and QA menus run only in the Unity editor. NUnit unit tests protect the rules. On 9 October all 138 tests passed.

A level is a small file made of letters

Each level is a small JSON file. The target pattern is written in letters: T turquoise, K cobalt, M coral, S saffron, Z emerald, and a dot for an empty cell. Clay, cages and moss sit the same way on a separate obstacle layer. spawnColors are the colours of tiles that fall onto the board (Servi has no saffron), and target is the rate the variable-choice bot should reach. This lets me change a level without writing code, in the editor or as text.

level_20.json · Servi’s file, shortened (obstacle layer and editor report removed)
{
    "name": "Servi",
    "moves": 31,
    "spawnColors": [0, 1, 2, 4],
    "rows": [
        ".......",
        "...Z...",
        "..ZZZ..",
        "..ZZZ..",
        ".ZZZZZ.",
        ".ZZZZZ.",
        "..ZZZ..",
        "...M...",
        "..MMM.."
    ],
    "target": 0.86
}

The code behind one tap

Blast calculates a tap’s result and applies it to the board. It also handles moves, wins, losses and moss growth. Preview makes the same calculation without changing the board. Bots use it to compare moves, so they test the same rules the player plays.

BoardModel.cs · one tap
public BlastResult Preview(int x, int y) =>
    State == GameState.Playing ? Resolve(x, y, previewResult) : null;

public BlastResult Blast(int x, int y)
{
    if (State != GameState.Playing) return null;
    var result = Resolve(x, y, moveResult);
    if (result == null) return null;

    MovesLeft--;
    MoveNumber++;
    Apply(result);
    RecountPainted();

    if (PaintedCount >= TargetCount) State = GameState.Won;
    else if (MovesLeft <= 0) State = GameState.Lost;
    else
    {
        if (result.MossDestroyed == 0) SpreadMoss(result);
        ShuffleUntilPlayable(result);
        if (!HasAnyMove()) State = GameState.Lost;
    }
    return result;
}

On screen: playing the same result in order

The screen code does not recalculate the rule. The board controller passes the tap to the model and hands the result to the class that runs the animation. It reads the cleared tiles, painted cells, obstacle hits, tools, falling tiles and moss from that result and animates them in order.

MoveChoreographer.cs · animation order
public void Play(BlastResult result)
{
    int width = board.Layout.Width;
    if (columnReady.Length < width) columnReady = new float[width];
    System.Array.Clear(columnReady, 0, width);

    float step = result.IsToolTap ? tuning.ToolStep : tuning.RippleStep;
    float gatherEnd = ClearTiles(result, step);
    RevealCreatedTool(result, gatherEnd);
    RecolorByBrush(result, step);
    HitObstacles(result, step);
    PaintCells(result, step);
    ActivateTools(result, step);
    ShakeForGroup(result);
    SendCollectables(result, step);
    DropTiles(result);
    ApplyShuffle(result);
    SpreadMoss(result);
}

Shared rules for the game and tests

The board model is separate from its visual presentation. Clearing, painting, falling, obstacles and tools are calculated in that model. Bots use it rather than a separate approximation of the game. Fixed seeds and CSV reports make changes measurable again.

The Level editor section explains the editing and balancing workflow. Sharing the board model helps avoid applying a rule differently in the editor, game and bot measurement.

Small rules that carry the feel of the game

Some design decisions are a few lines of code. When a new tile falls, there is a 30% chance it takes one of the target colours still waiting to be painted in that column. Otherwise it comes from the colour pool at random. The aim is that a player close to finishing a pattern does not get stuck waiting for the right colour. Tools depend on group size too: 7 or more tiles make a brush, 10 or more a bucket, and 13 or more a seal.

BoardModel.Apply.cs · the colour of a new tile
int SpawnColor(int column)
{
    if (Rules.SpawnBias > 0f)
    {
        biasedSpawnPool.Clear();
        for (int y = 0; y < Height; y++)
        {
            int cell = Index(column, y);
            int target = targets[cell];
            if (target >= 0 && painted[cell] != target && Array.IndexOf(spawnColors, target) >= 0) biasedSpawnPool.Add(target);
        }
        if (biasedSpawnPool.Count > 0 && random.NextDouble() < Rules.SpawnBias)
            return biasedSpawnPool[random.Next(biasedSpawnPool.Count)];
    }
    return spawnColors[random.Next(spawnColors.Length)];
}

Applying remote settings safely

Remote Config can change level-specific move budgets, a general move bonus, lives and economy values. Numeric inputs are clamped to allowed ranges. Level moves, for example, stay between 1 and 99. New settings take effect in the map flow rather than changing an active board halfway through.

This is infrastructure for controlled experiments, not evidence of growth. Ads and purchases can also be checked with test providers in the editor.

Interface and visual language

The game’s look comes from Turkish ceramic tiles: a turquoise background, a cobalt frame and raised tiles that shine like glazed ceramic. I rendered the tiles, obstacles and tools in Blender, and designed the interface and background in Figma. The images below come from the game’s own files.

Five colours, five flowers

Each colour has a distinct flower: round forget-me-not petals, a pointed bellflower, a layered rose, a thin-petalled daisy and a side-view lotus. I wanted shape to help identify tiles as well as colour, including when colours are difficult to distinguish or the screen is in sunlight.

  • Turquoise tile with a raised forget-me-notTurquoiseForget-me-not
  • Cobalt tile with a raised bellflowerCobaltBellflower
  • Coral tile with a raised roseCoralRose
  • Saffron tile with a raised daisySaffronDaisy
  • Emerald tile with a raised lotusEmeraldLotus
Top: the five tiles in the game. Bottom: the same tiles in greyscale. Without colour, the flowers still tell them apart.

Showing a target without relying on colour

Clearing the right colour paints the target underneath. Later wrong-colour blasts leave it complete. I use the visible strip around a tile to show the target. An unfinished cell has diagonal hatching. Once painted, the hatching disappears and the floor becomes glazed. This gives the player another cue besides colour.

Saffron tile on a plaster floor
Not a targetPlain plaster floor.
Coral tile on a floor with cobalt hatching
Waiting to be paintedCobalt target: light blue floor with cobalt hatching. The tile on top can be any colour.
Saffron tile on a cobalt-painted floor
DoneThe floor has turned cobalt and the hatching is gone.
Made by layering the game’s floor, hatching and tile files with the game’s own drawing rules.

Obstacles and tools

I rendered tiles, obstacles and tools with the same camera and lights. I wanted their looks to suggest their rules. A crack marks single-hit clay, while stacked layers mark double clay. The cage leaves the tile colour visible because it can still join groups. Moss looks alive and spreading. Brass and cream tools sit on plain tiles so their icons stand out.

  • Cracked clay obstacleClayOne hit
  • Two-layer clay obstacleDouble clayTwo hits
  • Cage with brass jointsCageHolds the tile in place
  • Lumpy green mossMossSpreads
  • Brush toolBrushGroup of 7–9
  • Paint bucket toolBucketGroup of 10–12
  • Seal tool with an eight-pointed starSealGroup of 13+

Fixing render quality

The first renders had harsh, jagged highlights. Stepped 8-bit height maps, very smooth glaze and small lights contributed to the problem. I smoothed the maps at higher precision, lowered the relief, softened the glaze and enlarged the lights. I rendered at twice the resolution and reduced to game size. Clay cracks became carved grooves instead of rods above the surface. I updated 19 images. Their sizes and import settings stayed the same, so texture memory did not increase.

Earlier cobalt tile: harsh, sharp highlight at the cornerBefore
New cobalt tile: soft highlightAfter
Plain cobalt tile: the harsh highlight is softer.
Earlier clay: cracks were rods sitting on the surfaceBefore
New clay: cracks are cut into the surfaceAfter
Clay crack, close up: the rod on the surface became a real groove.

Interface pieces

I designed the buttons, badges, frames, panels and icons in Figma. They share a navy outline, dark base and slightly offset light face. Buttons also have a highlight. I used 9-slice sprites for panels, frames and buttons so they stretch without changing the corners. The same panel appears in dialogs, the shop and the top bar.

  • Main button
  • Ad offer
  • Map badge
  • Locked badge
  • Frame
  • Panel
  • Life
  • Gold leaf
  • Lock
  • Settings
  • Watch ad
  • Navy #1B2E5E
    Outlines
  • Cobalt #2F63C6
    Frame, ad button
  • Green #47C65E
    Main button
  • Saffron #FFC93C
    Badge, lock, gold
  • Coral #F2545B
    Lives
  • Turquoise #2DB7AF
    Background

Screen layout and languages

The move count sits in a large circle beside the target preview. Restart and map controls sit below the board, outside the play area.

Menus, tips and offer text support 7 languages: Turkish, English, French, Italian, German, Spanish and Portuguese. Long offer text, target-preview legibility on small screens and the usefulness of non-colour cues still need checking on real devices and with players.

Next steps

I need to recruit real players for playtests to check how clearly they understand the target pattern, colour-matching rule and tools.

Overview

Crumble Crew is a collapse puzzle with 50 levels in five groups. You choose one of three coloured buttons, remove its target blocks and watch the structure settle. The puzzle comes from the order: the biggest hit now can leave a block standing at the end.

I wanted a short decision followed by a collapse you could watch clearly. Each group introduces one block rule, then combines it with earlier ones. I focused on the decision each level asks for, what the player needs to see to make it, and where to place a breather between harder levels.

My work on the project

  • Level design: I designed all 50 levels in an editor I built. For each of the five groups of ten, I set the new block, the tutorial levels and the finale.
  • Testing and improvements: I built a level checker and a bot test with 50 behaviour profiles. I examined the data with SQL and Excel, revised 7 levels and tested them again.
  • Product decisions: I defined the analytics events and measures, and set limits for Remote Config. The game has no ads or purchases. I plan to use its data to examine progression, learning and performance.
  • Implementation: I wrote the collapse rules in C# and Unity, modelled the blocks in Blender and designed the interface in Figma.

How I gathered the evidence

I could not access large-scale player testing, so I used bots with different playing behaviours. I report their assumptions alongside the results and compare changes under the same conditions. These are bot measurements. I have not yet checked their effect on real players.

ScopeTools
50 levels, 5 groups · Android and iOSUnity 6 · C# · Figma · Blender
Analytics, crash reports and remote settings · no ads or in-app purchasesFirebase Analytics · Crashlytics · Remote Config
Analysing the bot dataSQL · Excel
Menus and hints in 9 languagesEnglish, Turkish, Spanish, Portuguese, German, French, Italian, Russian, Indonesian

Gameplay

Each button hits blocks of its own colour. Its number shows how many it will hit now. Blocks fall when they lose support. Normal blocks falling two block heights or more can shatter and crush those below. You cannot undo a move, and the next button is hidden.

Learning the order first

The video shows the same level played twice. On the first try the moves run out and a single red block is left standing. On the second, that red block goes first and the last hit brings the whole structure down. The only difference is the order of the moves.

  • The first tap on a button lights up the blocks it will hit. The second tap knocks them down, so nobody wastes a move by accident.
  • The next button in line replaces the one used. The player only sees the three buttons on screen.
  • The level ends when every block is down. If the moves run out, the game says how many blocks are still standing, and “Try again” restarts the level.

A guide to the game screen

A screen from level 4. The numbers mark the elements described here.

Game screen of level 4: two dots at the top left showing the moves left, the level name and menu button at the top right, a bridge in the middle with its two red legs lit up, and blue 7, red 6 and yellow 6 buttons at the bottom. The image carries markers 1 to 5.
  1. 1Moves left. Each dot is one move. Two moves are left on this screen.
  2. 2Level name and menu. The menu pauses the game: continue, restart the level or go back home.
  3. 3Structure. The level itself: coloured blocks resting on the ground or on each other. The level is won when every block is down.
  4. 4Targeted blocks. One tap on a button lights up the blocks it will hit. Here, the two legs red 6 will knock down. The second tap knocks them down.
  5. 5Buttons. There are always three buttons. The colour says which blocks a button knocks down and the number how many it hits. The next one in line replaces the one used.

Game loop

  1. LookWhat is holding up what?
  2. PickTap a color. Its targets light up.
  3. HitThe second tap knocks them down.
  4. WatchEverything that lost its support collapses.

When moves run out, the game shows how many blocks remain. One tap restarts the level. With no undo and a hidden next button, I wanted a loss to encourage trying a different order.

Outside the level, the loop is a path: every finished level opens the next one, and a new block arrives every ten levels. When every level is done, a “Coming soon” box waits at the end of the path. The game has no ads or in-app purchases. What progress gives you is new structures and new blocks.

Level design

I split the 50 levels into five groups of ten. Each adds a block, teaches its rule, combines it with earlier blocks and closes with a finale. Older blocks stay in use. The last level brings steel, chameleon and two-tone blocks together. I set up the catalog and level selection so I can add another group when I develop a new block idea.

The five block types side by side: normal, steel, chameleon, anchor and two-tone.
Left to right: normal, steel, chameleon, anchor, two-tone.
Levels New block What it does The player’s likely new question
1–10 Normal Falls when its support is gone, and shatters if it falls far enough What is holding up what?
11–20 Steel Survives the fall and can’t be crushed by what lands on it. Only a hit of its own color breaks it What will be left after the collapse?
21–30 Chameleon Takes the color that was used last How will my move order change the colors?
31–40 Anchor Hangs in mid-air and holds up its neighbors What is this structure hanging from, if not the ground?
41–50 Two-tone Breaks in two hits, one for each of its colors In which order should I hit the same block?

Planning each group’s rhythm first

Each group starts with two short teaching levels. I then remove the hints and make the structures larger. The finale combines the decisions taught in that group. Teaching levels have at most three buttons and three moves. Here are four examples from the steel group.

LevelIts jobThe decision I expect from the player
11 · TutorialShow that steel doesn’t break when it fallsWatch the collapse and see the steel left intact in the rubble
12 · TutorialShow that falling blocks can’t crush steelBreak the uncrushed steel with a button of its own colour
13 · FreeAsk about the same rule, this time without a hintSee in advance which steel will still stand after the collapse
20 · FinaleClose the group with the narrowest solution: only 1 of 81 orders winsWork out the one right order from how the structure is supported

How I use the difficulty measure

I first look at how many tested move sequences win. Fewer winning paths leave less room for error. That describes the solution space. It does not measure how hard the level feels to someone. A single clear route can be easier to read than several misleading ones.

The number doesn’t measure how tempting the wrong orders look or whether the right line can be read from the screen. So I read it together with the bot result and the level’s job in its group. Level 14 (Pyramid) is an example of how I decide when the three don’t agree.

Design rules and the checking tool

I wanted regular levels to make move order matter, reward planning and leave the right path readable. My solver tries every move order and flags designs that may miss those goals. I inspect the flagged level in the game before deciding what to change. Teaching levels are exempt because their first job is to show a rule.

LevelAnalyzer.cs · the design rules, shortened
// Tutorial levels are there to show the rule; the design rules only apply to regular levels.
if (r.index < TutorialLevels || r.def.tutorial) return;

if (r.greedy.won) r.warnings.Add("The bot that picks the biggest number wins");
if (r.myopic.won) r.warnings.Add("The bot that only thinks about this turn wins: no long-term plan needed");
if (!r.greedy.won && r.greedy.percent > NearMissPercent && !r.greedy.onlyArmoredLeft)
    r.warnings.Add($"The bot that picks the biggest number loses by a hair ({r.greedy.percent}%): the loss may feel unfair");

// First moves: the winning move must not look too weak to notice, and the second decision must still matter.
if (bestWinningImmediate < maxImmediate * HiddenMathRatio)
    r.warnings.Add($"The winning first move looks very weak in the moment ({bestWinningImmediate} blocks, best immediate {maxImmediate}): hidden maths risk");
if (!standingOk)
    r.warnings.Add($"The structure left after the right first moves is outside {MinStanding}-{maxStanding}%: the second decision may stop mattering");
if (winnable == r.firstMoves.Count)
    r.warnings.Add("Every first move can win: the first decision doesn't matter");
  • Tapping the biggest number shouldn’t be enough. If a player who always taps the button with the biggest number can clear the level, the tool flags it. In a regular level, that means the order of moves has stopped mattering.
  • Planning should pay off. If always going for the option that knocks down the most blocks right now is enough to clear the level, the tool flags it. Then there’s no need to think ahead.
  • Simple paths should not lose by a hair. If the biggest-number or biggest-immediate-hit strategy destroys more than 90% of a structure but loses, the tool flags it. A near miss may encourage another try or feel unfair. Bot data does not tell me which. I use the warning to inspect the design.
  • The right first move should be readable. If the winning first move knocks down less than half of what the best-looking move does, the tool flags it. The player may only find it by luck. But the structure’s supports can also make such a move visible, so I check this warning by playing the level too.
  • Both decisions should matter. If every first move can win, the first decision stops mattering. If none of the right first moves leaves 30–70% of the structure standing, the second decision may stop mattering (the upper limit is 90% in levels with chameleon blocks). The tool flags both cases.

All 50 levels pass these checks without warnings. The solver knows the full button queue, though. Players see only three buttons. It missed Balcony’s information trap, which the bot test found.

Teaching a new block

Every new block is introduced with two short tutorial levels. Level 11 in the video is the first lesson on steel. I followed three rules when designing these levels:

  • Teach one thing per level. Steel has two properties, so I split them across two levels: in level 11 steel doesn’t break when it falls, and in level 12 the blocks that land on it can’t crush it.
  • Say it first, then let the player do it. A short hint states the rule and shows where to tap. The real proof is the collapse itself: everything falls apart and the steel block is left standing in the rubble.
  • Leave no room for mistakes. Tutorial levels have at most three buttons and three moves. The very next level asks the same rule again, this time without a hint.

Difficulty curve

The bars show each current level’s solution space. Colours distinguish teaching, regular and finale levels. I changed seven designs after bot testing. On 10 October, I checked all 50 again. Every level had a solution. The explored states had no unsupported starting blocks, zero-target buttons or duplicate choices. These are rule checks. Player difficulty has not been measured.

  • Tutorial level
  • Regular level
  • Finale
Show the data as a table
Level Winning orders All orders
1 3 7
2 4 27
3 1 9
4 1 9
5 2 27
6 3 27
7 3 81
8 2 81
9 2 81
10 2 81
11 1 2
12 1 2
13 3 27
14 2 27
15 3 81
16 4 27
17 5 81
18 2 81
19 9 81
20 1 81
21 2 2
22 2 2
23 5 27
24 2 27
25 3 79
26 4 27
27 4 81
28 4 81
29 7 77
30 1 81
31 1 1
32 2 2
33 4 25
34 2 27
35 3 81
36 4 27
37 4 81
38 2 81
39 3 27
40 1 81
41 2 2
42 6 6
43 4 27
44 3 27
45 3 81
46 5 27
47 3 81
48 2 81
49 7 81
50 2 81

Groups begin with two easy teaching levels, then grow harder in waves before the finale. I plan the dips too. A breather follows a hard level. At the start of a new group, I reduce pressure to leave space for the new rule.

Level editor

I designed all 50 levels in the editor I built in Unity. It keeps the structure, button queue, moves and design note together. I choose the decision, build the structure, run the checks and open the draft in the game before saving. Level 20, Viaduct, shows this workflow below.

Screenshot of the level editor in Unity: a list of 50 levels on the left, a structure of coloured blocks in the middle, the level's difficulty analysis on the right.
The level editor inside Unity with level 20 open. Level list on the left, canvas in the middle, live check on the right: only one of this level's 81 move orders wins. The editor's own interface is in Turkish.
  1. Build: I paint the structure on the grid and line up the buttons and the number of moves.
  2. Check: The live check tries every move order and immediately shows how many win and which design rule doesn’t hold.
  3. Play and fix: I open the level in the game without saving and look at how the collapse looks and whether the structure is easy to read on a phone.

The illustrated editor workflow

  1. I start from a shape. A bridge, a crane, a clock tower… that’s where the level names come from. What matters as much as the shape is what the player will have to decide there: which leg to cut first, whether to bring down the roof or the base.
  2. I draw the structure. I pick a block type and colour from the palette at the top and paint by dragging across the grid. Right click erases. Alt-click turns the block I clicked into my brush. If the structure outgrows the grid, I extend the grid from the left, the right or the top. A block with no support gets a red outline. I don’t want anything falling the moment a level opens.
    The block palette and the top of the grid in the level editor. Two floating blue blocks are outlined in red.
    Block types and colours at the top. The two blue blocks left floating are outlined in red.
  3. I line up the buttons. Each button has a colour and a number. The first three are on screen when the level opens and the rest arrive in order, so the order of the buttons is part of the puzzle too. Hovering a button shows me on the grid which blocks it would hit on the first move. This is also when I set how many moves the player gets.
    The lower part of Viaduct and the button order in the level editor. The six blocks red 6 would hit are outlined in white.
    Hovering red 6 outlines in white the six blocks it would hit on the first move. The button order is underneath.
  4. I read the live check. The panel on the right refreshes with every change and tells me: how many of all the move orders win, what the shortest solution is, whether a bot that always taps the biggest number or only thinks about the current move would win, and how much of the structure each first move leaves standing. Any condition that fails shows up right underneath as a warning.
    The right side of the level editor: part of Viaduct and the live check panel with 1 / 81, the shortest solution, both bot strategies, first-move options and warnings.
    Live check: 1 of 81 orders wins, both bot strategies lose, and every condition passes.
  5. I keep changing it until the numbers hold. This is the longest part, because a single block can change the whole level. In level 20 above, only one of the 81 move orders wins. Of the two legs in the middle, the right one has a blue block at its base. When I turn it yellow, that number goes up to 7 and the bot that always taps the biggest number starts winning too, so the level no longer meets the planning requirement I set for this finale (pictured). When I do the same to the middle block of the left leg, no order wins at all. I see both the moment I paint the block, without launching the game.
    Viaduct in the level editor with a single block changed: the live check shows 7 / 81 and gives five warnings.
    The same level with one difference: the block at the base of the right middle leg went from blue to yellow. Winning orders went from 1 to 7, and the panel lists exactly what broke.
  6. I check for a twin. If the level’s decision structure is too close to another level, the editor names that level. Then I change one of the two, or at least place them far apart in the game. Even if they look different, I don’t want the player solving the same puzzle twice.
  7. I try it in the game. The rule check matches my design targets. They don’t tell me whether it’s fun. “Play in game” opens the level straight in the game, and I look at whether the collapse looks good and whether the structure reads comfortably on a phone screen.
    Left: Viaduct drawn in the level editor. Right: the same level on a phone game screen.
    Left: the drawing in the editor. Right: the same level in the game.
  8. I put it in order. I move the level up and down the list until it sits in its place on the difficulty curve. The dot next to each level in the list shows whether it passes the check, and every level is checked once more when a build is made.
    Levels 15 to 32 in the level editor’s list and the bottom of the grid. The dot next to Viaduct is red, the others are green.
    The level list. Viaduct’s dot has turned red because a block was left floating.

The live check confirms solutions and catches repeated options. It knows the whole queue. It cannot tell me how someone will read only the three visible buttons. I use behavioural bots and visual checks to examine that part of the design.

Level 14: problem, decision, result

Pyramid is a three-move level in the steel group. Two of its 27 sequences win: red 9, one of the yellows, then blue 8. Only three buttons are visible at the start. Others appear as replacements after use. In the first bot test, planning profiles cleared it on their first try just 2% of the time. Current-move profiles cleared it 55%. A level intended to reward planning was doing the opposite in this model.

Old order

On screen932Next382
  1. Taps red 9. Red 3 takes its place. Yellow 3, yellow 2 and red 3 are on screen.
  2. Working it out with what’s on screen, red 3 looks like the best follow-up: with yellow 3 after it, 84% of the structure comes down, while going yellow first reaches 61% at most. Taps red 3.
  3. Only now does blue 8 appear. It knocks down 97% of the structure, but the level is lost.

The planner loses

New order

On screen932Next832
  1. Taps red 9. Blue 8 takes its place. Yellow 3, yellow 2 and blue 8 are on screen.
  2. Working it out with what’s on screen, yellow and then blue 8 brings down the whole structure. Taps yellow.
  3. Blue 8 brings down what’s left, and the level is won.

The planner wins

The map is the same in both. Only the places of two upcoming buttons changed. Framed blocks are steel. The steps follow a bot that plans two or three moves ahead.

The problem: the last piece of the winning line wasn’t on screen

In the old queue, blue 8 appeared only after the second move. A bot planning with the visible buttons chose the best continuation it could see and lost. For a current-move bot, red 3 and yellow 3 looked equal: each destroyed 9% of the structure. Half chose yellow, then used the newly visible blue 8 and won. That equal choice, rather than planning, led to the winning path.

The change: swap the order, leave the map alone

I wanted the visible buttons to make the winning continuation readable. Planning should help, while choosing the largest immediate hit should not be enough. I also wanted to keep the same solution space: two winning sequences out of 27.

I swapped queued red 3 and blue 8 and kept the structure unchanged. Blue 8 now appears after the first move. The bot can see that using it after yellow brings everything down. Using blue 8 immediately still loses: 22% of the structure remains, and the last move cannot clear it.

Before choosing this change, I ran two other candidates through the same test and ruled them out:

  • Turning the rightmost block of the red beam yellow. The first-try clear rate for planning bots rose to 62%, but only 1 of 27 orders won, so the level got harder on paper. Level 14 isn’t a finale, so I ruled it out.
  • Turning yellow 3 into yellow 1. Again only 1 of 27 orders won, and planning bots (16%) still won less often than the ones looking only at the current move (about 25%). It didn’t fix the problem.

The result and what I learned

Planning bots’ first-try completion rose from 2% to 42%. The number of winning paths stayed at two out of 27. Across all profiles, completion rose from 22% to 28%, so the opening still needs work. I chose the queue change because it supports planning without narrowing the solution space. The test and its limits are in Bot A/B comparisons.

Tests and balancing

I used two tests: isolated levels to inspect their decisions, and an in-order run to see where the simulated progression stalls. The results in this section are the historical baseline before the seven changes. They are kept separate from the current catalog check and the before/after comparisons.

Bot profiles and test method

I made 50 bot profiles with different lookahead, error, memory and patience settings. Each uses the game’s rule code and sees only the three available buttons. It does not know the next button. The profiles differ in the following ways:

  • Lookahead. Thinks one, two or three moves ahead.
  • Carelessness. How often it picks the wrong one when two options look close.
  • Big-number pull. How strongly it’s drawn to the button with the biggest number.
  • Memory. How well it remembers a losing opening on the next attempt, and whether it remembers which buttons come next.
  • Patience. How many losses in a row on the same level before it quits the game.

Each profile played 20 times. I repeated the test with four separately generated groups of 50 profiles: 4,000 runs in total. I used the groups to examine how much the findings depended on the profile mixture. I chose the behaviour settings myself. They have not been calibrated with player data, so I use the bots to find design problems.

Tests and measures in this section

Level test
Every bot plays every level separately, with up to 30 attempts to clear it. Shows how hard a level is on its own. The charts and the results in the decision table come from this test.
In-order test
Bots play the game from the start, level by level, and quit when they lose the same level too many times in a row. Shows where bots drop off. The SQL and Excel work uses this test’s data.
Attempts per clear
Attempts per clear: total attempts on a level ÷ number of bots that cleared it. Attempts by bots that never cleared it or quit are included, so it isn’t the same as the average of the clearing bots’ own attempt counts.
First-try clear rate
Share of the bots starting a level that clear it on their first attempt.
Drop-off
Share of the bots reaching a level that quit the game there (in the in-order test).
Near miss
An attempt lost with only 1–2 blocks left.

Four groups × 50 profiles × 20 repeats give 4,000 runs per layout in the isolated-level test. In the sequential test, 4,000 bots start the game and fewer reach later levels. These are repeated assumed behaviours, not 4,000 human participants. The spreadsheet contains the first sequential run, without raw runs for the later revisions.

What the first test revealed

The difficulty measure and the bots’ attempts per clear largely move in the same direction (rank correlation 0.89, where 1 is a perfect match). The real value came from the levels that don’t fit the measure:

  • Medium by solution count, hard for the bots. Fountain (44) has two winning orders out of 27. Levels with that same count take 3–4 attempts per clear, but Fountain takes 9.5. Two losing openings look very similar to the winning one. Solution count does not show that similarity.
  • An information trap. Balcony (5) needs a button that is not visible yet. Even a bot choosing the best visible move loses on its first try. Only 3% clear it immediately. The solver knows the full queue and missed this problem. Its place early in the game made it a priority to inspect.
  • Planning works against the bot. In Pyramid (14), current-move bots clear 55% of first attempts. Bots looking two or three moves ahead clear only 2%. I wanted planning to help, so I revisited the queue.
  • An early wall. Crane (10) is the first group’s finale. In the sequential run, 46% of arriving bots stop here. I tackled this bottleneck before later levels because it ends the first group.
  • A middle level harder than the finale. Ship (level 28) takes even more attempts than its group’s finale, which breaks the group’s pacing.
  • Levels where players get stuck. In Robot (level 47), 10% of the bots can’t solve it even in 30 attempts. In Skyline, the last level, a third can’t.

Attempts per clear for all 50 initial layouts

Each column is one level. Its height is total attempts divided by bots that cleared the level, including attempts by bots that never cleared it. The chart shows the designs before the changes.

  • Other levels
  • Problem level

Attempts per clear

Show the data as a table
Level Attempts per clear Cleared on first try
1 · First Smash 1.0 100%
2 · Shell 1.6 60%
3 · Right Support 1.4 66%
4 · Bridge 1.4 71%
5 · Balcony 3.7 3%
6 · Twin Bridge 2.0 37%
7 · Layers 3.3 42%
8 · Castle 3.7 12%
9 · Twin Towers 2.8 33%
10 · Crane 8.3 10%
11 · Steel Roof 1.0 100%
12 · Steel Floor 1.0 100%
13 · Stairs 2.2 45%
14 · Pyramid 3.7 22%
15 · Clock Tower 5.0 22%
16 · I-Beam 2.3 36%
17 · Factory 4.3 24%
18 · Aqueduct 7.7 9%
19 · Gear 2.5 31%
20 · Viaduct 8.2 8%
21 · Color Shift 1.0 100%
22 · Shifting Stilts 1.0 100%
23 · Mushroom 1.8 46%
24 · Hourglass 3.3 19%
25 · Windmill 4.2 27%
26 · Crown 3.4 55%
27 · Turtle 3.1 20%
28 · Ship 11.2 6%
29 · Flower 1.8 53%
30 · Cathedral 9.0 11%
31 · Anchor Beam 1.0 100%
32 · Anchor Shelf 1.0 100%
33 · Balloon 3.1 21%
34 · Floating Islands 3.8 20%
35 · Kite 4.6 23%
36 · Sky Bridge 1.5 64%
37 · Airship 5.2 19%
38 · Cable Car 6.3 13%
39 · Satellite 1.8 50%
40 · Floating Castle 8.1 16%
41 · Two Coats 1.0 100%
42 · Tough Roof 1.0 100%
43 · Totem 1.5 71%
44 · Fountain 9.5 14%
45 · Rocket 8.6 7%
46 · Zigzag 1.6 61%
47 · Robot 8.8 20%
48 · Train 3.5 24%
49 · Lighthouse 3.0 32%
50 · Skyline 20.9 12%

SQL and Excel

In the in-order test run before I edited seven levels, 4,000 bot runs played 126,873 attempts. I put the level_start and level_end events the bots generated into three simple tables and asked questions with SQL. The queries run in SQLite.

  • players: one bot per row: how many moves ahead it thinks (lookahead), how many levels it cleared, whether it finished, and the level where it quit (stopped_at_level).
  • attempts: one attempt per row: the level, the attempt number, whether it was won (won is 1 or 0) and how many blocks were left.
  • levels: each level’s number and name.

1. Where are we losing the most bots?

SQL · bot test data
-- Botları en çok hangi level'da kaybediyoruz?
SELECT p.stopped_at_level AS level,
       l.name,
       COUNT(*) AS bots_quit
FROM players p
JOIN levels l ON l.level = p.stopped_at_level
WHERE p.finished = 0
GROUP BY p.stopped_at_level, l.name
ORDER BY bots_quit DESC
LIMIT 5;
levelnamebots_quit
10Crane1149
5Balcony559
7Layers360
18Aqueduct248
8Castle239

What the query does: It takes the bots that did not finish, groups them by the level where they quit and counts the bots in each group. The JOIN brings each level’s name from the levels table. The five largest counts are listed.

What I found: Most bots quit on Crane, level 10: 1,149 bots, or 45.6% of the 2,520 bots that reached it. Crane is the first group’s finale, so it was the first place I examined. Second on the list, Balcony (level 5), turned out to hide an information trap.

2. Are the losses near misses?

SQL · bot test data
-- Kaybedilen attempt'lerin ne kadarı kıl payı (1–2 blok kaldı)?
SELECT a.level,
       l.name,
       COUNT(*) AS losses,
       ROUND(100.0 * SUM(CASE WHEN a.blocks_left <= 2 THEN 1 ELSE 0 END) / COUNT(*), 1) AS near_miss_pct
FROM attempts a
JOIN levels l ON l.level = a.level
WHERE a.won = 0
GROUP BY a.level, l.name
HAVING COUNT(*) >= 100
ORDER BY near_miss_pct DESC
LIMIT 5;
levelnamelossesnear_miss_pct
35Kite60695.0
37Airship79786.1
16I-Beam116680.3
14Pyramid352572.3
40Floating Castle97166.9

What the query does: It looks only at lost attempts: won = 0. For each level it counts the losses. CASE WHEN counts a loss with 1–2 blocks left as 1 and any other loss as 0. The total is divided by the number of losses to give a percentage. HAVING leaves out levels with fewer than 100 losses, because a percentage from little data misleads.

What I found: On Kite, 95% of losses leave one or two blocks. Coming this close may encourage another try or feel unfair. I need to watch someone play to investigate that response.

3. Can a bot that plans ahead win on Pyramid?

SQL · bot test data
-- Pyramid'de (14. level) plan yapan botlar ilk attempt'te kazanıyor mu?
SELECT p.lookahead,
       COUNT(*) AS bots,
       ROUND(100.0 * SUM(a.won) / COUNT(*), 1) AS first_try_win_pct
FROM attempts a
JOIN players p ON p.player_id = a.player_id
WHERE a.level = 14 AND a.attempt_no = 1
GROUP BY p.lookahead
ORDER BY p.lookahead;
lookaheadbotsfirst_try_win_pct
139058.7
25282.3
34371.8

What the query does: It takes the first attempts on Pyramid (level 14). How far ahead a bot thinks is stored in the players table, so the JOIN connects the two tables and the result is split by lookahead. Because won is 1 or 0, SUM(won) is the number of wins. It is divided by the number of bots.

What I found: In the progression data, current-move bots win 58.7% of first attempts on Pyramid. Planning bots win only 2%. This gave me another reason to change the queue.

Excel workbook

I also examined the data in Excel. Attempts stores one attempt per row. Levels calculates results with COUNTIFS. For example, counting a level number together with attempt 1 gives the bots that started it. Entering a level number in Segment’s yellow cell recalculates first-try completion by lookahead.

Excel · two formulas from the Levels sheet
C2 · bots that started the level: rows with attempt 1 on that level
=COUNTIFS(Attempts!C:C, A2, Attempts!D:D, 1)
K2 · near miss: lost attempts with 1–2 blocks left, divided by losses
=IFERROR(COUNTIFS(Attempts!C:C, A2, Attempts!E:E, 0, Attempts!G:G, "<=2")/J2, 0)
The Levels sheet in Excel: bots starting and clearing each level, bots quitting there, drop-off, attempt counts and near misses, with a drop-off chart on the right.
The Levels sheet in Excel. The drop-off column is colour-coded. Crane and Balcony stand out. Select the image to enlarge it.

Level summary from the Excel analysis

The table below contains the 50-level summary from the first campaign simulation: 4,000 starts and 126,873 attempts, before the seven edits. This is a campaign dataset. It differs from the tests that start each level independently. Scroll within the table to read all levels.

LevelRuns reaching itReach from 4,000 startsDrop-off after reaching itAttempts per clearFirst-try clearNear misses in losses
1 · First Smash4,000100.0%0.0%1.0099.9%0.0%
2 · Shell4,000100.0%1.9%1.6162.0%0.0%
3 · Right Support3,92498.1%0.2%1.4067.0%31.4%
4 · Bridge3,91597.9%0.2%1.3769.0%0.0%
5 · Balcony3,90797.7%14.3%3.893.5%43.3%
6 · Twin Bridge3,34883.7%1.5%2.0037.7%0.0%
7 · Layers3,29982.5%10.9%2.6544.6%20.9%
8 · Castle2,93973.5%8.1%3.3813.4%0.0%
9 · Twin Towers2,70067.5%6.7%2.5934.7%0.0%
10 · Crane2,52063.0%45.6%9.258.0%20.0%
11 · Steel Roof1,37134.3%0.0%1.00100.0%0.0%
12 · Steel Floor1,37134.3%0.0%1.00100.0%0.0%
13 · Stairs1,37134.3%1.2%2.0148.6%18.5%
14 · Pyramid1,35533.9%10.9%3.9218.4%72.3%
15 · Clock Tower1,20730.2%15.7%4.1325.4%38.1%
16 · I-Beam1,01825.4%1.2%2.1642.8%80.3%
17 · Factory1,00625.1%9.2%3.5827.5%10.7%
18 · Aqueduct91322.8%27.2%6.559.1%45.9%
19 · Gear66516.6%1.2%2.4626.9%51.4%
20 · Viaduct65716.4%21.8%6.477.8%51.1%
21 · Color Shift51412.8%0.0%1.00100.0%0.0%
22 · Shifting Stilts51412.8%0.0%1.00100.0%0.0%
23 · Mushroom51412.8%0.0%1.6251.4%22.7%
24 · Hourglass51412.8%0.8%2.5519.8%0.0%
25 · Windmill51012.8%4.5%3.0737.8%7.9%
26 · Crown48712.2%2.3%2.0573.3%0.0%
27 · Turtle47611.9%0.6%2.5622.9%8.3%
28 · Ship47311.8%31.1%8.344.7%13.4%
29 · Flower3268.2%0.3%1.5061.7%0.0%
30 · Cathedral3258.1%20.9%6.2214.8%35.9%
31 · Anchor Beam2576.4%0.0%1.00100.0%0.0%
32 · Anchor Shelf2576.4%0.0%1.00100.0%0.0%
33 · Balloon2576.4%0.0%2.2630.0%61.3%
34 · Floating Islands2576.4%4.7%3.2021.0%10.8%
35 · Kite2456.1%6.5%3.6528.6%95.0%
36 · Sky Bridge2295.7%0.0%1.3074.2%0.0%
37 · Airship2295.7%11.8%4.9521.4%86.1%
38 · Cable Car2025.1%10.4%4.9915.3%34.2%
39 · Satellite1814.5%0.0%1.6358.6%44.7%
40 · Floating Castle1814.5%25.4%8.199.9%66.9%
41 · Two Coats1353.4%0.0%1.00100.0%0.0%
42 · Tough Roof1353.4%0.0%1.00100.0%0.0%
43 · Totem1353.4%0.0%1.1691.1%0.0%
44 · Fountain1353.4%14.8%4.7125.2%43.3%
45 · Rocket1152.9%7.0%4.753.5%25.7%
46 · Zigzag1072.7%0.0%1.2085.0%0.0%
47 · Robot1072.7%0.0%2.2241.1%0.0%
48 · Train1072.7%0.9%2.6438.3%19.0%
49 · Lighthouse1062.6%0.9%2.3833.0%15.9%
50 · Skyline1052.6%3.8%4.6315.2%54.2%

Reach uses the 4,000 campaign starts. Drop-off and first-try completion use the runs that reached the level. Attempts per clear is all attempts divided by successful runs, including attempts from runs that never cleared. A near miss is a loss with one or two blocks left, divided by lost attempts. The original worksheet stores bot-run IDs under “player”. None of these rows are human participants. COUNTIFS counts entries and wins, then ratios use those counts.

Changes based on bot tests

I did not choose a change only because it reduced attempts. It also needed to preserve the level’s role, remain solvable and pass the button checks. The results below are rounded summaries of the earlier bot comparisons. No new player measurements are included.

I tried small changes on seven levels: a block colour or two, or a button swap. First I ran the checker. No button should target zero blocks, no two should target the same blocks, and choosing the biggest number alone should not win. Bots then tested the designs that passed. I chose an option that kept the level’s role, saved it and wrote the reason in its design note.

What changed in the 7 levels?

Level What the data showed Decision Result (with bots)
5 · Balcony Only 3% of bots clear it on the first try. The solution depends on a button that isn’t on screen yet Two blocks in row 4 swapped colours Attempts per clear 3.7 → 2.9. First-try clears 3% → 10%
10 · Crane The first finale. 46% of the bots that reach it quit here Two buttons swapped places, a block in the bottom row turned red Attempts per clear 8.3 → 5.4. First-try clears 10% → 28%
14 · Pyramid Looking only at the current move wins (55%), planning ahead loses (2%) Two buttons swapped places On the first try, 42% of planners and 4% of current-move players clear it
28 · Ship Harder than its group’s finale: 11.2 attempts on average A block in the bottom row turned blue Attempts per clear 11.2 → 3.9
44 · Fountain Medium on paper, 9.5 attempts for the bots. Misleading first moves One block turned yellow Attempts per clear 9.5 → 5.3. First-try clears 14% → 36%
47 · Robot 10% of bots can’t solve it even in 30 attempts Two buttons swapped places, a block in the bottom row turned blue Attempts per clear 8.8 → 3.8. Unsolved after 30 attempts 10% → 0.1%
50 · Skyline The last level. A third of bots can’t solve it even in 30 attempts Two buttons swapped places, one block turned yellow Attempts per clear 20.9 → 7.4. Unsolved after 30 attempts 35% → 7%
  • Before
  • After
Show the data as a table
Level Attempts per clear (before → after) Cleared on first try Unsolved after 30 attempts
5 · Balcony 3.7 → 2.9 3% → 10% 0% → 0%
10 · Crane 8.3 → 5.4 10% → 28% 3% → 0%
14 · Pyramid 3.7 → 2.8 22% → 28% 0% → 0%
28 · Ship 11.2 → 3.9 6% → 24% 7% → 0%
44 · Fountain 9.5 → 5.3 14% → 36% 5% → 0%
47 · Robot 8.8 → 3.8 20% → 21% 10% → 0%
50 · Skyline 20.9 → 7.4 12% → 24% 35% → 7%

Pyramid and Robot still have the same number of winning sequences. Planning bots now do better on Pyramid. Removing Robot’s misleading opening cut attempts per clear by more than half. The bots’ decisions changed without widening either solution space.

After retesting

  • The share of bots that reach Crane and quit there fell from 45.6% to 29%.
  • Runs reaching level 11 rose from 34% to 47%, and those reaching the last level from 2.6% to 4.3%.
  • The bottleneck moved: the level where the most bots quit is now Aqueduct (level 18). 35.6% of the bots that reach it quit there.
  • In Balcony, the bots clearing it on the first try rose from 3% to 10%, which is still low for the game’s fifth level.

Crane and Skyline changed both button order and a block colour. Their comparison evaluates the whole new layout. It cannot isolate which edit produced the difference. The simulated reach figures also depend on the bots’ patience settings and are not retention rates.

Bot A/B comparisons

Could showing a button sooner help planning without changing the solution space? I tested that on Pyramid. A uses the old queue. B swaps the upcoming red 3 and blue 8. Structure, three-move budget, starting buttons and rules stay the same.

Test choiceDefinition
HypothesisShowing blue 8 after the first move could help planning bots see the winning continuation.
Main measureFirst-try clear rate for profiles with two- or three-move lookahead. One-move profiles and the full profile mix are reported separately.
Design constraintsKeep 2 winning paths out of 27. Retain the need to choose a move order. Do not narrow the solution space just to raise one segment’s score.
Test scopeHistorical isolated-level comparison: four groups × 50 profiles × 20 repeats, 4,000 runs per layout. These are repeat simulations of assumed profiles.
Old order New order
Winning move orders 2 of 27 2 of 27
Cleared on first try: planning bots 2% 42%
Cleared on first try: current-move bots 55% 4%
Cleared on first try: all bots 22% 28%
Attempts per clear 3.7 2.8

I chose B. Planning profiles gain 40 percentage points in first-try completion. Current-move profiles fall from 55% to 4%. Planning helps more in this bot model. Across all profiles the gain is just 6 points, so I still want to inspect the opening.

Bots played this comparison. I did not assign human participants to random groups. The percentages are rounded summaries of the earlier test. On 10 October I checked again that both queues have two solutions out of 27. That rule check does not rerun the behaviour measurements. I draw no conclusion about player conversion, statistical significance or retention from it.

Why I rejected “just add a move”

I also tried adding a move to a hard level. It can reveal zero-target or duplicate buttons at the end of the queue. A larger budget therefore needs checking before I treat it as a valid option. I made the fixes in the levels instead. A Remote Config budget change needs the same check.

Analytics and product decisions

I added analytics for the questions I want to investigate when player data arrives. The table shows what I would measure and review. I have no player results yet. Local bot files stay separate from Firebase player events.

QuestionEvents and measuresDecision to investigate
Where does progression stall?level_start / level_end / level_quit · unique starts, clears, attempts and exitsSeparate first attempts, replays and unfinished sessions. Inspect the decision and visible information before testing a layout change.
Do losses cluster around one or two blocks?level_end · blocks_left, moves_used, attemptInspect the remaining block and the preceding choices. A near miss does not tell me whether someone wants to retry or feels treated unfairly.
Does the first tutorial flow make sense?tutorial_begin / tutorial_complete · completion flowObserve how players interpret the first tap and target preview, then try a shorter hint. A missing completion event alone does not explain the cause.
Is an exit related to performance?quality_tier, level_quit and Crashlytics · device and version contextCompare the same level across device groups and inspect frame behaviour. Treat an association as a question to investigate.
When should I ask for a rating?review_prompt · request point at level 10 or the endCheck whether the moment interrupts progression. The event records a request. It does not prove the store displayed a prompt or a review was submitted.

Getting the denominator right

The game records the attempt number on the device. A level-end event carries the result, used and remaining moves, remaining blocks and duration. Restarting and switching levels are separate exit reasons, and leaving before making a move is not counted as a played-level quit. An app closing can leave a start without an end, which needs its own interpretation.

Using the same metric definitions will let me compare bot and player results later. The groups are still different. Day-one and day-seven return rates need dated player cohorts and enough observation time. Bots reaching later levels do not measure that.

Economy and product choices

Crumble Crew has no ads, in-app purchases, virtual currency or lives system. The progression is built around solving and retrying the puzzle, unlocking the next level and meeting a new block every ten levels. I can therefore assess a layout’s baseline difficulty without a paid continuation changing the result.

The visible reward is the collapse and the next stop opening on the path. I chose that progression loop. I have not measured whether it brings players back or how another business model would perform. My analytics plan focuses on progression, learning and performance.

Technical details

One rules model for the game and the checks

The grid model calculates falling and breaking. Physics shows the debris. A block gets support from the ground, a stable block below or a stable neighbour within two cells in the same row. Unsupported connected blocks fall together. A one-cell fall lands safely. Larger falls can shatter normal blocks and crush those below. The calculation repeats until the structure settles. Special blocks add their own rules.

The editor and deterministic checks use this same model. Levels are separate data assets in an ordered catalog. The UI Toolkit editor supports undo, groups a drag stroke into one undo step and waits briefly after painting before checking the latest draft. Before a build, the catalog is checked for missing or unsolvable levels.

Remote settings and their limits

Remote Config can add 0–2 moves globally, change level budgets, label test groups and toggle the rating prompt. Budgets stay at or above the designed value and cannot exceed five. Teaching levels keep their own budgets. Missing settings use defaults, and changes apply at the next level. These limits do not prevent zero-target buttons on their own. Each candidate still needs a level check.

Collapse effects within a phone budget

I modelled a block split into five pieces in Blender. Debris comes from a reusable pool, with at most 100 active pieces on a phone. Further breaks use dust when the pool is full. Pieces do not collide with each other. I use these limits to control cost, without claiming a measured frame rate on every device.

Screen shake follows block mass and fall height. Impacts of six or more blocks add 0.05 seconds of hit stop. The final collapse slows to 35% speed for half a second. Haptics add an impact cue on Android and iOS. I use these effects to make the result of a move readable while preserving the structure as the focus.

Keeping test records out of player analytics

Behavioural bots run locally and do not connect to Firebase. Editor and measurement modes suppress analytics collection. Measurement mode also avoids remote tuning and rating prompts. Crash reports include the current level as context. This keeps a test run from being counted as a player session.

Interface and visuals

I designed the interface in Figma and blocks in Blender. Their appearance also shaped the levels. I built the structures and options around the information a player can read on screen.

Three buttons, 81 possibilities

I drew the interface on a 1170 × 2532 phone screen in Figma. Three buttons stay at the bottom, each about a quarter of the screen wide and within thumb reach. They show only a colour and a large number.

The three-button decision also set the size of the puzzle. On every move the player picks one of three options, and the next button in line takes the place of the one used. That’s why a 2-move level has at most 9 move orders, a 3-move level 27 and a 4-move level 81. The difficulty measure in Level design is built on exactly these numbers.

The number shows current targets and updates when the structure changes. The first tap also highlights those blocks. To protect that information, I added two checks: buttons must have a target, and visible buttons must not target identical blocks. The editor flags both as errors. Neither was found in the current 50 levels.

The pieces on screen

I drew a small set of interface pieces and reused them. Move buttons use lighter block colours with a dark lower edge. Green marks primary actions and white marks secondary ones. Orange dots show remaining moves, becoming faint empty circles after use. A white pill sprite becomes a level label, path or hint bubble when stretched and coloured. Card titles use a yellow ribbon.

  • Red button
  • Blue button
  • Yellow button
  • Main button
  • Secondary button
  • Menu
  • Pill
  • Title ribbon
  • Card
  • Move left
  • Move used
  • Tutorial hand
  • Red #FF4D3A
    Block and button
  • Blue #2B7CFF
    Block and button
  • Yellow #FFC41F
    Block and button
  • Green #35A358
    Ground
  • Steel #8C949E
    Steel block frame
  • Night blue #143A63
    Menu background

Framing the structure for the available screen area

The camera fits each structure between the top bar and bottom buttons, allowing for safe areas and wide-screen margins. Bridges and small towers share that usable area. I still need to check small displays and long translations on real devices.

Blocks that show their own rule

I modelled the blocks in Blender. I wanted their appearance to give a clue to each rule. I used a different feature for each special block:

  • The steel block is wrapped in a metal frame: it doesn’t break when it falls.
  • The chameleon block has a three-colour wheel on top: after every move it turns the colour of the button you just pressed.
  • The anchor block is held by a steel clamp: it stays put even when there’s nothing under it.
  • The dual-colour block is split down the middle: it takes two hits to break, one for each colour.

There is very little text in the play area. Buttons show colour and numbers. Menus and hints support nine languages, while the puzzle rules stay the same. I aim to introduce each block in two short levels. I still want to observe how much the visual cues help players learn.

The broken version of the block was modelled in Blender too: the block is split into pieces in advance, and in the game those pieces fly apart when it breaks. How they move, and how they’re capped so the phone doesn’t struggle, is covered above in Technical details.

A red block made in Blender, next to the same block’s five pieces scattered in the air.
In Blender: a single block and the pre-split version that replaces it when it breaks.

The app icon: 31 attempts

I made 31 app-icon renders in Blender. I started with single cubes showing chipped corners, cracks or broken pieces. Then I tried a stack in the game’s three colours, changing its arrangement and background. I chose a slightly offset top cube to suggest an unstable structure. I also checked the silhouette. Three stacked cubes remain readable without colour. The wordmark uses the same colour family.

  • Red cube with a piece broken off its cornerChipped corner
  • Red cube with a cracked surfaceCracks
  • A pyramid of red, blue and orange cubes on navyThree cubes, pyramid
  • Three cubes stacked diagonally on navyDiagonal stack
  • Black silhouette of the chosen arrangementSilhouette check
  • Three cubes on blue with the top cube slightly offFinal icon
Six of the 31 renders, from left to right in the order I tried them.
Crumble Crew wordmark with letters shading from red to orange and blue
The wordmark.

A level select built to grow

Level select screen: a zigzag path on a dark blue background, level boxes from 46 to 50 and a box reading “Soon” at the end of the path.

Level select is a path: every level is a stop, and the next level waits behind a lock. When every level is finished, a “Soon” box appears at the end of the path and the end screen says “New levels are coming soon!”

The path builds itself from the catalog’s level count. When I add a group in the editor, level selection updates without a new interface layout.

Next steps

I need to recruit real players for playtests to observe how they plan with the three available buttons and understand support relationships and special-block rules.