Laya beat my Rust game. So did random picks.
/ 25 min read
Last Updated:I built a simple Rust game (not my first one) and wanted to see whether Laya could actually play it. I’d seen Jev play Minecraft, and small models play games like Snake, so I wanted to test that hype on a game of my own.
I kept the game small on purpose. You enter a room, kill the enemies and find the next room, until the whole floor is clear. There’s no inventory and no long quest, so when a bot fails, there’s nothing else to blame.
The game is a prototype in the style of The Binding of Isaac. When a fight starts, the doors close, and they open again once every enemy is dead. Chasers walk toward the player and hurt it on contact. To win, a bot has to clear every combat room. Surviving until the clock runs out doesn’t count.
Laya gets the room as structured data, not screenshots. The game pauses while Laya thinks, so a slow answer never leaves the player standing still while an enemy kills it.
What one Laya call looks like
Text and a list of answers go in. One score per answer comes out. This is a real call from a fight later in the post.
Code sends
State
Goal: clear all combat rooms. Stay alive. … Health: 3. Phase: combat. Clearance now/goal, tiles: enemy 11.46/4; wall 0.1/2. Clearance goal unmet: yes. Chaser 0: health 2, distance 11.46, aligned false. Chaser 1: health 2, distance 22.03, aligned false. Chaser 2: health 2, distance 15.56, aligned false. …
Question
Create space when clearance is inadequate; otherwise focus a nearby weakened chaser.
Answers
focus_0: Retain 0; position/fire to defeatfocus_1: Retain 1; position/fire to defeatfocus_2: Retain 2; position/fire to defeatcreate_space: Seek clearance; nearest fire
Laya returns
focus_09.7%focus_18.9%focus_212.4%create_spacepicked69.0%
Code takes the top answer and acts on it. Here, Rust starts moving the player away from the wall.
This experiment had one neural model in it. I ran the exact English Laya version linked above on my own Mac, through MLX on Apple Silicon. Jev was part of the inspiration, but I never tested it. I did no training or fine-tuning for the game. Every change below is a change to the prompt, to how the answer turns into an action, or to the code around the same weights.
Auto-aim wasn’t enough
Laya died even with auto-aim on.
In one run, it had one health left and its back against the east wall. A chaser was coming in diagonally. Laya kept picking stay and firing west. Then it picked stay again. A sixtieth of a second later, the chaser killed it.
Rust already aimed the gun. It fired north, south, east or west, whichever way pointed at the nearest enemy, and here that was west. But Laya still chose where to stand, and pointing the gun west didn’t make that spot safe.
To know if Laya was any good, I needed something to compare it with, so I wrote two bots with no model at all. The rules bot follows a few plain Rust rules and always does the same thing in the same situation. The random bot picks from the allowed actions at random, using a fixed sequence of random numbers so every run can be repeated.
All three bots got the same help from Rust: the same aiming, the same steering through doorways and the same menu of short actions. Each action ran for at most a sixth of a second of game time (ten physics steps, at 60 a second), and then the bot chose again.
Where Laya sits: short moves
Each layer runs inside the one above it. Laya picks the route and the move. Rust only aims and steers.
Objective whole floor
Clear every combat room. The game sets this.
Goal none held
Nothing keeps a target or plan between calls. Each call starts again from the room state.
Route next doorway
Pick an open doorway or wait. The next call, a sixth of a second later, can pick a different doorway.
Move up to 1/6 s
In combat, stay or move in one of eight directions, always firing.
Physics step every 1/60 s
Aim, steer onto the chosen doorway, run physics.
I ran all three on the same ten maps, with the same physics and a limit of 120 seconds of game time. Time spent waiting for Laya didn’t count against that limit. The rules bot cleared every floor and never took a hit. Laya and the random bot cleared none.
Laya against the rules bot and the random bot
The first ten-map test, before skills.
| Bot | Wins | Deaths | Out of time |
|---|---|---|---|
| Rules bot | 10/10 | 0 | 0 |
| Laya | 0/10 | 3 | 7 |
| Random bot | 0/10 | 0 | 10 |
Its rules were simple. When exploring, it went to the room it had visited least. In fights, it scored each move on how much space it left from chasers and walls, and on lining up a shot. That was just the shape of the room at that moment. It didn’t predict the physics like the controller I built later.
In a different recorded fight near the south wall, the rules bot moved northeast while firing north. A sixth of a second later, it had almost lined up with the chaser. Then it stayed still and fired. In that spot, staying was useful. In Laya’s east-wall fight, staying killed it.
The random bot failed in a different way. In one run, it kept changing where it was going until time ran out. It never killed an enemy or cleared a room.
In three small fight tests, separate from the full game, Laya cleared the room without taking damage, and so did the rules bot. The random bot cleared them too, but took damage. Laya could also walk through a doorway, or survive a short test. But on a full floor, it still died or ran out of time.
Maybe it’s my prompt
Honestly, most of the time Laya was just dancing around in the starting room. At that point, I was pretty sure the problem was my prompt.
That was a fair place to look. Laya got the same big dump of data every time: positions, enemies, exits and its last few actions. Much of it didn’t matter. In a fight, it still read about closed doors it couldn’t use. While exploring, it saw how often it had visited each room next door, but not whether a cleared room led anywhere new.
So I stopped sending the same dump every time. Before each call, the game code looked at what was happening and built the prompt to match. A small behavior tree does that check, built with Bonsai, a Rust library. It works out whether the player is moving between rooms, fighting or exploring, and then picks what goes into the prompt. In a fight, Laya sees the enemies, the walls and the shots. While exploring, it sees the open doors and a map of the rooms the player has already seen, so it can tell a dead end from a door that still leads somewhere new.
The map holds only what the player has seen, not the hidden layout of the floor, and nothing was trained.
There was a second problem, and the prompt had nothing to do with it. Laya picked a door, the bot started walking, and a sixth of a second later Laya could pick a different door. At the start of one run, it picked south, then west, then north, and each new pick undid the last few steps. Even a good pick could look wrong on screen, because to reach a west door, the bot first has to walk north to line up with it.
So in the next version, Rust kept Laya’s door until the bot got there, or until the trip went wrong, like getting stuck or taking too long. Rust already knew how to line up with a door, and now it got to finish the walk. This changed how the bot moved, not just what Laya read.
Full floors · 10 per version
A better prompt, then keep the door
Do a better prompt and keeping the door win more floors?
-
A · Original prompt
0/10 wins 3 deaths 7 out of time
-
B · Prompt for the situation + map
1/10 wins 3 deaths 6 out of time
-
C · B + keep the door
1/10 wins 4 deaths 5 out of time
Limit. B and C win only the same single map. Neither gets close to 8 wins out of 10.
What changed, with prompt excerpts
Each version: the same ten maps, 120 seconds of game time, same model, physics, allowed moves and auto-aim. C also changes how the bot walks to a door.
A · Original prompt
Every position, every door, visit counts and recent moves. Pick again every sixth of a second.
Clear every combat room. Explore open doorways. Stay alive.
Axes: east +x, south +y. Chasers hurt on contact.
tick=0 room=0 bounds=-16,16
player position=0,0 velocity=0,0 health=3
exit North position=0,-16 sealed=false visits=0 B · Prompt for the situation + map
Only what matters right now: fighting, exploring or changing rooms. A remembered map shows which doors are unexplored, dead ends or a way through.
Choose the action that advances clearing ALL remaining combat rooms. Explore unmapped doorways or reach unexplored branches. Repeating cleared dead ends or waiting cannot win.
North option text:
Head North to explore an unmapped doorway; short step, then choose again; no fire C · B + keep the door
Keep the chosen door until the bot arrives or the trip fails. Fights still use short moves.
North option text:
Head North to explore an unmapped doorway; retain target until arrival or interruption; no fire Together, the new prompt and the map took wins from zero to one. Keeping the door won that same map again, and no other. Keeping a door only helps if it’s the right door, and neither change touched the fights.
Asking the question a different way didn’t help either. When I asked straight out which door led somewhere new, Laya gave the useful doors higher scores, but it didn’t pick them any more often. That test and two others are in “The other prompt experiments” below.
Fix one failure, find another
The fights were the bigger problem. My first guess was the words. Maybe “move north” was hard to use when the gun was firing west, so I tried names relative to the gun, like “sidestep right relative to aim”. The moves behind the names stayed the same. New descriptions fixed some bad choices and broke some good ones. New names made it worse.
So maybe Laya couldn’t turn coordinates into facts. Rust could. The next prompts added lines like “blocked moves east; sliding moves ne, se”, plus whether a chaser stood in the firing line. They described the room right now. They never said which move was safe.
Saved fight moments · 47 per version
Spell out the walls and the aim
Do wall and firing-line facts prevent damage?
Saved moments replayed one at a time, not full games.
-
A · Original
38/47 took no damage
- Fixes
- 0
- New damage
- 0
-
B · Blocked + sliding moves
42/47 took no damage
- Fixes
- 6
- New damage
- 2
-
C · Firing line
37/47 took no damage
- Fixes
- 2
- New damage
- 3
-
D · Both
39/47 took no damage
- Fixes
- 2
- New damage
- 1
Fixes count the 9 moments where the original prompt took damage. New damage counts the 38 where it didn't.
Limit. The best version still made 2 new damaging choices. 4 of its 6 fixes came where no move was blocked, so the wall facts may not be why it improved.
What changed, with prompt excerpts
The same 47 saved fight moments and compass moves. Only facts about right now are added, never which move would be safe.
A · Original
Positions, wall distances, motion, health and cooldowns.
Walls: north 11.83, south 20.17, east 0.35, west 31.65.
Chaser 2: 0.27 south, 0.95 west; motion 1.49 north, 5.3 east. Health 2. B · Blocked + sliding moves
Add which moves are blocked or slide along a wall right now.
Wall contact now: blocked moves east; sliding moves ne, se. Blocked moves can still fire. C · Firing line
Add whether a chaser is in the current firing line. Not a prediction of a hit.
Current West firing lane: nearest chaser 2 is inside it. Current geometry, not a hit prediction. D · Both
B and C together.
Wall contact now: blocked moves east; sliding moves ne, se. Blocked moves can still fire.
Current West firing lane: nearest chaser 2 is inside it. Current geometry, not a hit prediction. The best version still made two new choices that took damage. Here’s one. The player had its back against the east wall, and a chaser was about one tile away, closing in from the west. With the original prompt, Laya picked north, ran straight up along the wall and took no damage. With the wall facts added, it picked northeast and got hit a thirtieth of a second later.
Northeast at the east wall
A saved moment from a test run, not current gameplay footage.
- P · Player radius
- 0.35 tiles
- C · Chaser radius
- 0.55 tiles
- Chaser relative to player
- 0.95 west
0.27 south - Player center to east wall
- 0.35 tiles
The wall blocks the east half of the move, so northeast slides north more slowly than a plain north move. Arrows show directions, not future paths.
North: no damage over the next sixth of a second
Northeast: hit a thirtieth of a second later
Exact prompt excerpts
Original prompt
Walls: north 11.83, south 20.17, east 0.35, west 31.65. Chaser 2: 0.27 south, 0.95 west; motion 1.49 north, 5.3 east. Health 2.
Added wall facts
Wall contact now: blocked moves east; sliding moves ne, se. Blocked moves can still fire.
A diagonal move splits its speed between two directions. The wall blocks the east half, so northeast is really just a slow north, and the chaser catches up. The prompt even said northeast would slide, but it never said what sliding means here. To get from “slides along the wall” to “slow north, and the chaser catches me”, you have to think it through, and Laya reads the prompt once and scores the answers. Every word I added was true, and it still made this fight worse. Describing the room is not the same as knowing what each move does over the next few moments.
On full floors, the wall facts stopped the deaths, but now the bot ran out of time on nine floors. It was walking in circles. At one point, it walked back into a cleared room it had already visited 153 times, while two other doors were still unexplored. Adding a short list of its recent door trips to the prompt didn’t stop it. The bot walked to each door just fine. Moving wasn’t the problem. Choosing where to go was.
Laya kept walking back into the same cleared room because that door was always its top answer. But Laya doesn’t give just one answer. It scores every door. So I had an idea: what if the bot picked a door at random, weighted by those scores? A door with a lower score would still get picked now and then, and that might break the loop. I changed only that, and only while exploring. In fights, the bot still took Laya’s top answer.
Full floors · 10 per version
Pick at random, weighted by Laya's scores
Can Laya's own scores break the loops?
-
A · Top answer
1/10 wins 0 deaths 9 out of time
Totals across all 10 floors
- Rooms reached
- 20
- Rooms cleared
- 20
- Room revisits
- 2,762
-
B · Weighted random pick
1/10 wins 9 deaths 0 out of time
Totals across all 10 floors
- Rooms reached
- 31
- Rooms cleared
- 22
- Room revisits
- 128
Early-death warning. Revisits and model calls drop partly because 9 runs die early. One random sequence per map. The only win is still on the same map.
What changed, with prompt excerpts
Same ten maps, doors kept until the bot arrives, wall facts on, 120-second limit. While exploring, every allowed choice, wait included, goes into one random pick weighted by Laya's scores. Fights still take the top answer.
A · Top answer
Always do Laya's top exploring choice.
B · Weighted random pick
One weighted random pick per new exploring choice. Same prompt, no second tries, no anti-loop rule.
The nine floors that ran out of time became nine deaths. The number of wins didn’t change.
So the weighted pick worked, in a way. The bot stopped circling cleared rooms and walked into new fights. That was the problem: walking in circles had been keeping it away from fights it couldn’t win. In one of those deaths, the player had one health left and a chaser was just over a tile to the south. The prompt said to avoid contact, and Laya chose to move east anyway. A twentieth of a second later, the run was over. Room revisits fell from 2,762 to 128, but dead bots don’t revisit rooms. Technically I fixed it. It just isn’t progress.
The bot now failed in a new way, but it still failed. Better inputs, clearer wording and a weighted random pick had not taught Laya to survive a fight while it picked its own short moves.
The other prompt experiments
Three more tests from the same stretch. None of them changed the story, so they’re tucked away here.
Saved exploring moments · 32 per version
Four ways to ask for an unexplored door
Does a narrower question or plainer wording fix the choice?
Saved moments replayed one at a time, not full games.
-
A · Original
27/32 headed somewhere new
Average score on useful doors 76.65%
-
B · Narrower question
27/32 headed somewhere new
Average score on useful doors 81.07%
-
C · Plainer door text
27/32 headed somewhere new
Average score on useful doors 76.62%
-
D · Plainer room text
27/32 headed somewhere new
Average score on useful doors 76.35%
Limit. All four fail on the same five moments. The narrower question raised Laya's scores for useful doors, but it didn't pick them any more often.
What changed, with prompt excerpts
The same 32 saved exploring moments, with the same doors in the same order. Only the wording changes. Each version answered three times per moment.
A · Original
The original question and dead-end wording.
Question:
Choose the action that advances clearing ALL remaining combat rooms. Explore unmapped doorways or reach unexplored branches. Repeating cleared dead ends or waiting cannot win.
West option text:
revisit a cleared room with no known unexplored branch
Room description:
No known unexplored branch without returning here. B · Narrower question
Change only the question. Everything else stays.
Which doorway offers an unknown connection or a known route to an unexplored branch? C · Plainer door text
Rewrite how a dead-end door is described. Nothing else changes.
Before:
revisit a cleared room with no known unexplored branch
After:
revisit a cleared room; further exploration returns here D · Plainer room text
Rewrite the dead-end sentence in the room description. Not combined with C.
Before:
No known unexplored branch without returning here.
After:
Further exploration requires returning here. Saved fight moments · 47 per version
Rename the moves, keep the physics
Are moves named relative to the gun easier to pick than compass directions?
Saved moments replayed one at a time, not full games.
-
A · Compass moves
38/47 took no damage
- Fixes
- 0
- New damage
- 0
-
B · Descriptions relative to the gun
41/47 took no damage
- Fixes
- 6
- New damage
- 3
-
C · Names relative to the gun
27/47 took no damage
- Fixes
- 0
- New damage
- 11
-
D · Both
32/47 took no damage
- Fixes
- 1
- New damage
- 7
-
E · D + room relative to the gun
32/47 took no damage
- Fixes
- 1
- New damage
- 7
Fixes count the 9 moments where the original prompt took damage. New damage counts the 38 where it didn't.
Limit. New descriptions fixed 6 of the 9 bad moments but broke 3 of the 38 good ones. No version fixed something without breaking something. These are 47 short moments, not 47 games.
What changed, with prompt excerpts
47 saved fight moments, picked around times Laya took damage. Laya had chosen badly in 9 of them. Same nine moves, auto-aim and option order. Every moment had at least one safe move.
A · Compass moves
The original move names and descriptions.
Firing West:
north: Move north; fire West B · Descriptions relative to the gun
Only the descriptions change. Names and room description stay in compass terms.
north: Sidestep right relative to aim; fire. C · Names relative to the gun
Only the names change. Descriptions stay in compass terms.
sidestep_right: Move north; fire West D · Both
Names and descriptions both relative to the gun.
sidestep_right: Sidestep right relative to aim; fire. E · D + room relative to the gun
Also describe the room relative to the gun. Same distances.
Walls: right 11.83, left 20.17, behind 0.35, ahead 31.65.
Chaser 2: 0.27 left, 0.95 ahead; motion 1.49 right, 5.3 behind. Health 2. Full floors · 10 per version
Try the fixes on full floors
Do fewer bad moves turn into more wins?
-
A · As before
1/10 wins 4 deaths 5 out of time
-
B · Wall facts
1/10 wins 0 deaths 9 out of time
-
C · Recent door trips
0/10 wins 4 deaths 6 out of time
-
D · Both
0/10 wins 2 deaths 8 out of time
Limit. Wall facts stop the deaths but leave 9 floors that run out of time. The list of recent door trips does not stop the back-and-forth. No version reaches 8 wins out of 10. This run went ahead even though the wall facts had failed the fight test.
What changed, with prompt excerpts
Each version: the same ten maps, 120 seconds of game time, doors kept until the bot arrives, Laya's top answer taken. Same model, physics and allowed moves.
A · As before
Same prompt as the keep-the-door version.
B · Wall facts
In fights, tell Laya which moves are blocked or slide along a wall. Exploring is unchanged.
C · Recent door trips
While exploring, list the last four door trips and the door the bot came in through.
Prompt excerpt:
Recent room crossings: room 0 -> room 2; room 2 -> room 0; room 0 -> room 2; room 2 -> room 0.
Entered current room 0 through North doorway from room 2.
Laya picked: North, the way back, even though South was unexplored. D · Both
B and C together. Nothing filters or replaces Laya's choice.
How do the Snake and Minecraft bots do it?
Going in, I’d have argued Laya was the more advanced model, ahead of Jev. So this made no sense to me. I’d seen Jev play Minecraft, so why couldn’t Laya clear these much simpler rooms every time?
So I read the code behind the public Snake and Minecraft bots I could find. I couldn’t find an official one from TypeSafe, so these are all community projects. The results below are what their builders reported. I didn’t run them again myself.
| Bot | The model picks | Code works out before asking | Code does after |
|---|---|---|---|
| Jev Minecraft, rmalde | One task from a list: walk to a waypoint, mine, craft, loot or fight | A separate language model sets the current goal. The route was mapped out in advance. | A bot library does the walking and mining. A reflex can cancel any task the moment dragon’s breath gets close. |
| Jev Minecraft, ellistev | Quarter-second steps and 30° turns | Code checks the blocks around the player: clear, blocked or dangerous. Unsafe moves are left out. | An answer is thrown away if it took over 5 seconds, or if the player moved while waiting. |
| Jev Snake, sorrycc | The next square | Deadly moves are removed. Moves into dead ends are marked. | One square per turn. |
| Laya Snake, mizorewww | The next square | A planner labels each option, for example “Safe. Best route to food.” | A safety check swaps an unsafe pick for the safe move Laya scored highest. |
| Laya vs Jev race | Steer hard left, left, straight, right or hard right, and whether to sprint | Where the apple is, relative to the snake’s head | Nothing. Walls wrap around and the snake can cross itself, so it cannot die. |
The Laya Snake port has the strongest result. It ran 8,160 moves with zero deaths, and the safety check overruled Laya only four times. But its planner had already labelled the best move in every list, so that result belongs to the planner and Laya together. It also used the multilingual version of Laya, not the English one I used.
The ellistev Minecraft bot is the closest match to my experiment, because its builder tried it both ways. When the model picked whole tasks, the bot built a 338-block flag in 590 seconds, with 131 decisions. When the model steered every quarter-second step, the same flag took 801 seconds and 1,848 decisions. The README says direct control can stall, and the bot can briefly wobble back and forth.
A bug report on Laya’s own GitHub shows the same limit from the other side. Each option already said what would happen. Laya still gave right a 53% score, even though the text for right said the snake would hit the wall and the game would end. The maintainer replied that Laya is “a single-pass classifier, not a planner”. It reads the options once and scores them, without thinking ahead. He suggested removing the deadly moves in code. That report used the multilingual version too.
My short-move bot had none of that help. Nothing removed deadly moves before the question, nothing caught them after, and nothing marked the best option. I was using the wrong tool for the job.
I hadn’t specialised Laya for this game, and none of this shows what was happening inside the model. But it told me what to try next.
So I did what the demos do. Instead of new names for the same tiny moves, Laya would pick whole tasks, and Rust would do the walking.
Laya picks the goal, Rust does the rest
Instead of a direction, Laya now picked a goal: fight this chaser, make some space, or go explore that door. Rust held on to the goal and did the moving.
That’s a lot more help than aiming the gun and lining up with doors. It changed Laya’s job. The model stayed the same, and nothing was trained.
To fight a chaser, Rust kept that target, moved to where it could hit it, fired, and stayed out of reach. Laya no longer had to pick northeast, then stay, then northeast again, just to line up one shot.
Making space had no target. Rust moved away from enemies and walls, and kept shooting at whatever was closest.
For both goals, Rust tried all nine moves, 60 times a second. It played each move forward about a fifth of a second, using the game’s real movement and combat code. Staying alive came first. After that, a fight move scored on lining up shots on its target from a useful distance, and a space move scored on how much space it bought.
Exploring changed too. Laya now picked an unexplored door anywhere on the part of the map the player had seen, even a few rooms away. Rust found the way there through rooms it already knew, and walked the whole trip without asking Laya again.
Where Laya sits: skills
Laya moves up one layer. Rust takes everything below it.
Objective whole floor
Goal up to 1 s in fights, 30 s walking
Fight a chosen chaser, make some space, or explore a chosen door. Rust keeps the goal until it's done, fails or runs out of time.
Route rooms to the door
Find the way through rooms the player has seen. No new question in between.
Move picked again 60 times a second
Play each of nine moves a fifth of a second forward with the real game code. Staying alive comes first.
Physics step every 1/60 s
Aim at the chosen chaser, or at the nearest one while making space.
Rust still didn’t see the hidden map, and it never swapped Laya’s choice for its own. But “move northeast for a moment” had become “kill this chaser without getting hit”. Dodging, positioning and finding the way were now the code’s job.
The skill version went one step further. Before every call, code rebuilt the whole question from the game state: what Laya read, what it was asked, and which answers existed at all. An answer only showed up if Rust could carry it out right then. The create_space answer showed up only while the player was too close to a wall or an enemy. When a chaser died, its focus answer disappeared.
The prompt is rebuilt before every call
Four calls in a row from the start of one run, about three seconds of play. Code decides what Laya reads, what it is asked and which answers exist.
Start of the floor
Laya reads
Phase: exploration. Frontier 0 North: route crossings 1. Frontier 0 East: route crossings 1. …
Question
Explore an unresolved frontier with a short known route.
Answers
First fight, pinned to a wall
Laya reads
Phase: combat. Clearance now/goal, tiles: enemy 11.46/4; wall 0.1/2. Clearance goal unmet: yes. Chaser 0: health 2, distance 11.46, aligned false. …
Question
Create space when clearance is inadequate; otherwise focus a nearby weakened chaser.
Answers
A moment later, with room
Laya reads
Phase: combat. Clearance now/goal, tiles: enemy 8.6/4; wall 2.1/2. Clearance goal unmet: no. Chaser 0: health 2, distance 8.6, aligned false. …
Question
Create space when clearance is inadequate; otherwise focus a nearby weakened chaser.
Answers
Chaser 0 is dead
Laya reads
Phase: combat. Chaser 1: health 2, distance 13.69, aligned false. Chaser 2: health 2, distance 9.02, aligned false. …
Question
Create space when clearance is inadequate; otherwise focus a nearby weakened chaser.
Answers
That meant the comparison had to change too. If I put this bot against the old random bot, Laya would get the credit for all the new Rust code. So all three bots got exactly the same skills, and only the picking was different.
The rules bot got new rules for picking goals. If it was too close to something, it made space. Otherwise it went for the nearest chaser it could kill in one hit, or just the nearest one. When exploring, it picked the unexplored door with the fewest rooms to walk through. The skills did the moving.
The random bot picked evenly from whatever skills were on offer. It ran five times on each of the ten maps, with different random numbers each time. That’s fifty runs, but still only ten maps.
Same skills, three ways to pick them
70 wins, none with damage, on 10 development maps.
| Picked by | Wins | Average seconds | Model calls |
|---|---|---|---|
| Laya | 10/10 | 21.56 | 175 |
| Rules bot | 10/10 | 19.73 | 0 |
| Random bot | 50/50 | 21.89 | 0 |
The random bot ran five times on each map with different random numbers: 50 runs on the same ten maps, not fifty different maps.
- Laya
- Rules bot
- Random bot, five runs
Time to clear the floor, in seconds of game time. Lower is faster.
A dotted line joins Laya and the rules bot on each map. The five diamonds are the random bot's five runs. Rows are spread out only so markers don't overlap; all 70 times use the same 0–45 second scale.
Laya was 8.4% slower than the rules bot, comparing the two map by map. Dividing the table's averages gives a different number.
Against the random bot, Laya was 1.5% faster, but the likely range runs from 8.7% faster to 6.2% slower. That is no real difference.
All 70 times, unrounded
Seconds of game time, not rounded. The random bot's runs are listed in the order they ran.
Map 1
- Laya:
22.283333333333335 - Rules bot:
22.333333333333332 - Random bot run 1:
24.2 - Random bot run 2:
24.116666666666667 - Random bot run 3:
23.866666666666667 - Random bot run 4:
23.45 - Random bot run 5:
22.566666666666666
Map 2
- Laya:
24.05 - Rules bot:
19.716666666666665 - Random bot run 1:
25.15 - Random bot run 2:
21.05 - Random bot run 3:
27.666666666666668 - Random bot run 4:
20.7 - Random bot run 5:
31.783333333333335
Map 3
- Laya:
21.133333333333333 - Rules bot:
13.933333333333334 - Random bot run 1:
16.166666666666668 - Random bot run 2:
15.083333333333334 - Random bot run 3:
18.316666666666666 - Random bot run 4:
15.75 - Random bot run 5:
19.666666666666668
Map 4
- Laya:
23.733333333333334 - Rules bot:
24.133333333333333 - Random bot run 1:
24 - Random bot run 2:
23.883333333333333 - Random bot run 3:
23.833333333333332 - Random bot run 4:
23.333333333333332 - Random bot run 5:
23.683333333333334
Map 5
- Laya:
14.216666666666667 - Rules bot:
14.216666666666667 - Random bot run 1:
14.016666666666667 - Random bot run 2:
12.6 - Random bot run 3:
13.633333333333333 - Random bot run 4:
13.65 - Random bot run 5:
14.066666666666666
Map 6
- Laya:
16.783333333333335 - Rules bot:
17.133333333333333 - Random bot run 1:
17.816666666666666 - Random bot run 2:
16.95 - Random bot run 3:
17.433333333333334 - Random bot run 4:
17.3 - Random bot run 5:
17.416666666666668
Map 7
- Laya:
38.7 - Rules bot:
32.75 - Random bot run 1:
33.46666666666667 - Random bot run 2:
30.1 - Random bot run 3:
35.31666666666667 - Random bot run 4:
40.11666666666667 - Random bot run 5:
33.983333333333334
Map 8
- Laya:
15.2 - Rules bot:
15.416666666666666 - Random bot run 1:
19.166666666666668 - Random bot run 2:
21 - Random bot run 3:
16.433333333333334 - Random bot run 4:
19.516666666666666 - Random bot run 5:
14.683333333333334
Map 9
- Laya:
17.1 - Rules bot:
17.65 - Random bot run 1:
16.916666666666668 - Random bot run 2:
17.25 - Random bot run 3:
17.516666666666666 - Random bot run 4:
17.333333333333332 - Random bot run 5:
16.683333333333334
Map 10
- Laya:
22.35 - Rules bot:
20.05 - Random bot run 1:
25.016666666666666 - Random bot run 2:
29.983333333333334 - Random bot run 3:
26.116666666666667 - Random bot run 4:
32.2 - Random bot run 5:
28.65
Laya cleared all ten floors. The rules bot cleared all ten. The random bot cleared all fifty runs. Not one of the seventy runs took a single hit.
The random bot could win now because it was choosing chasers and doors, not moves. Before, it could turn back from a door almost right away, or step the wrong way next to an enemy. Now a random chaser came with Rust code that walked up, shot and dodged. A random door came with a route that got there. And the random bot only ever saw answers that made sense right then, because code had already removed the rest. Each skill had a time limit, but it ran long enough to get real work done before the next choice.
So the bot worked. I still hadn’t shown why it needed Laya.
Laya won no more floors, and it was 8.4% slower than the rules bot, comparing the two map by map. It was no faster than the random bot either.
Those are times in game time. The game pauses while Laya thinks, so they leave out the waiting. The rules bot and the random bot never wait at all, because they don’t call a model.
The game now
Clips from the current game
The clip of Laya picking skills is at the start. Both 22-second clips were recorded on October 5. They show the current controls, not the September tests.
Laya picking short moves
The game has changed since September, so these clips show how the controls work. They don’t repeat the comparison. In the current game, the skill version won once, in 20.35 seconds of game time, with nineteen calls to Laya and no damage.
The September results also came from development maps, the same maps I’d already used to figure out what was going wrong. I had fresh maps held back, and I never used them. So the result I have is: reliable on those ten maps. It’s not a claim about every floor the game can generate.
Takeaways
What the working demos do
- Do the geometry in code. The Snake demos work out dead ends, reachable squares and the route to food before they ask the model. TypeSafe’s Jev guidance says it plainly: “Keep the arithmetic in code.” My first bot sent raw coordinates and wall distances.
- Remove or replace deadly choices. The Jev Snake removes deadly moves before it asks. The Laya Snake checks the answer afterwards and swaps it if it’s deadly. My short-move bot did neither, so the deadly stay at the east wall went straight through.
- Offer actions that already work. In Minecraft, “travel to the waypoint” calls a pathfinder, code that finds the route. My skills became the same kind of action.
- Let code keep checking. The Minecraft dragon’s breath reflex can cancel an action at any moment, without asking the model. My game paused while Laya thought, so speed was never the problem. A short fight move ran for up to a sixth of a second. Rust stopped it after damage or a room change, but it never predicted danger and picked a safer move. The skill code does that 60 times a second.
- Send only the facts the question needs. In the Flappy Bird game from the Laya vs Jev arena, a description of both the gap and the bird made Laya answer with the bird’s position, 0/6. Describing only the gap gave 6/6.
- Show how much the code does. The Laya Snake port counts how often its safety check stepped in. ellistev labels direct-control runs and task runs separately. My rules bot and random bot do the same job: they show what the code achieves without the model.
All of these demos gave the model more help than my first bot had. For the skills, I borrowed the task-level Minecraft design: the model picks a goal, and code handles the movement.
What my prompt changes actually did
The version that worked did two things at once, so I can’t split the credit:
- Ask for goals, not moves. Skills with time limits cleared 10/10 floors with no damage, against 0/10 for short moves.
- Rebuild the whole question from the game state before every call. Code chose what Laya read, what it was asked and which answers existed. Laya never saw an answer Rust couldn’t carry out right then. One of the Minecraft demos does the same: gathering and building offer different actions.
Three changes helped a little. Two of them also broke cases that used to work:
- A prompt built for the situation, plus a remembered map. Together they raised floor wins from 0/10 to 1/10. I changed both at once, so I can’t split the credit.
- Blocked moves in words. The line “blocked moves east; sliding moves ne, se” took saved fight moments from 38/47 to 42/47 without damage, with two new choices that took damage. On full floors, deaths fell from 4 to 0, but wins stayed at 1/10.
- Moves described relative to the gun. Moments without damage went from 38/47 to 41/47, with three new choices that took damage.
Three changes did not help. A narrower exploring question raised Laya’s score on useful doors from 76.65% to 81.07%, but it picked correctly in the same 27 of 32 moments. Naming the moves relative to the gun dropped fights from 38/47 to 27/47 without damage. A list of recent door trips took floor wins from 1/10 to 0/10.
These habits kept the results honest:
- Test wording on saved moments, with a rule against new damage. It caught the cases above that got worse, which better totals would have hidden.
- Change one thing at a time. Names, descriptions and the order of the options each moved the results. Reversing the order of the exploring options fixed three moments and broke two.
- Don’t ask when there is only one answer. In the first set of runs, 3,590 of 6,817 calls offered only a forced wait. The skill version skips those calls.
- Fit the prompt without cutting it. The English version reads at most 512 tokens (word pieces) per question. A check before sending rejects a shortened option or a cut-off instruction, then checks the real input after it’s split into tokens. Skill prompts used 150 to 249 tokens.
The fight question that worked was short. It asked for one judgment, and every answer named something Rust could finish:
Create space when clearance is inadequate; otherwise focus a nearby weakened chaser.
focus_0: Retain 0; position/fire to defeat
create_space: Seek clearance; nearest fire
Protocol and limits
Each comparison kept its settings fixed. Every full-floor run used the same ten development maps and a 120-second limit in game time. Physics ran 60 times a second and paused while Laya answered. Within one comparison, every bot got the same maps, rules, moves and helpers. Later comparisons changed what Laya saw or how moves ran, on purpose, so the results are a series of tests, not one long A/B test.
The first rules bot used no randomness. When exploring, it went to the least-visited door, and broke ties in the order North, South, East, West. In fights, it tried a two-tile move in each direction and scored it on distance from the nearest chaser, lining up a shot, and distance from the walls. It didn’t simulate the next few moments like the later skill code did. The first random bot used one fixed sequence of random numbers per map. The skill test gave the random bot five fixed sequences per map, drawn without bias.
The model was the English Laya version c5d78730f3493e4fe16d61507ef4b78eef7318cf, run through laya-apple 1.5.0 on the Mac’s GPU in 16-bit floats. It reads at most 512 tokens per question. I didn’t test the multilingual or typed-decisions versions in the game. Every input and action was recorded, so runs can be replayed without asking Laya again. The bot never quietly swapped in a rule when Laya failed to answer.
Laya saw the whole room, not just what a camera near the player would show. These tests had no enemy bullets, bosses or items. In the small separate tests, walking through any door counted as a navigation success, and staying alive until time ran out counted as survival. Neither meant clearing a floor.
The wording and fight tests used saved moments from five of the development maps. Laya answered each moment three times per version, to check that it gave the same answer each time. A wording change had to get more moments right, break none that were right before, and cause no errors. Every fight moment had at least one safe move. A fight change had to fix at least one moment that took damage and break none. The wall facts also had to give the same answer every time and reproduce the original results exactly. No version passed. Changing option names or order also reached 28 of 32 exploring moments, but reversing the order fixed three and broke two, so I didn’t count those as wording fixes.
For the skill test, the bot had to win at least eight of ten floors with no crashes. Some full-floor tests ran with changes that had failed the saved-moment tests. Running them didn’t make those changes safe. The weighted random test used one fixed random sequence per map, could pick wait, and still took Laya’s top answer in fights. Its revisit and call counts are lower partly because runs ended early.
For Laya to count as worth using, it had to beat the random bot by at least 20 percentage points in wins, and be ahead with 95% confidence. Or, with equal wins, it had to be at least 10% faster than the random bot on four of the five random runs, again with 95% confidence that it was faster at all. Laya met neither bar. The statistics treated each map as one unit, kept the random bot’s five runs on that map together, and compared times map by map instead of dividing averages.
Only a version that passed both bars would have run on ten fresh maps I had held back. It would have needed eight wins there too, with no tuning on those results. Those maps were never used, because Laya didn’t pass. The saved-moment tests also held back moments from the other five maps, for any change that passed. None did. Later full-floor tuning used those same maps, so those held-back moments aren’t fresh anymore. The skill version also did not replace the short-move bot as the game’s default.
A later short-move test, under changed game rules, gave Laya 0/10, the rules bot 9/10 and the random bot 0/10. The short-move clip in this post was cut off, not run to the time limit. The current clips and the one current skill win don’t add to the September counts.
I started with a model choosing small moves and Rust helping it aim. I ended with the same model choosing goals and Rust working out how to reach them. The second bot won. Then the random bot picked its goals, and it kept winning.