ScreepsAiSLP Build Log · Part 8 of 8
ScreepsAiSLP Build Log – Part 8: With a Little Help from My Friends
"That's cool, but you're missing some stuff"
I was telling a friend about this project: the AI server, the agent that codes my Screeps bot by itself, the firewall, the tests, the roads disaster from Part 7. He listened, said it was cool, and then said the thing friends say: "You're missing some stuff."
He didn't mean a feature. He meant two habits that people who build and train software for a living take for granted, and that I had only half figured out on my own:
- Measure first. Before you try to make something better, decide what "better" means as a number, measure where you are, change one thing, and measure again. That's the core loop behind machine learning.
- Tests first. Write the test that describes "done" before anyone writes the code. That's test-driven development.
Then, instead of just talking about it, he took a look at the project and sent me new rules and a new piece of test equipment: a scorecard that measures the colony every single tick. This post is about what he showed me, and why it changed how the agent works.
Measure first: the loop behind machine learning
When people train an AI model, the computer doesn't "understand" its way to a better model. It runs a very simple loop, millions of times:
- Pick a number that means "better". In machine learning that's the loss or the score: how wrong the model was on a set of examples.
- Measure where you are. That's the baseline.
- Change something a little.
- Measure again, the same way, on the same kind of examples.
- Keep the change if the number improved. If it got worse, don't keep piling changes on top.
That's it. The whole field is clever ways of doing step 3, and careful ways of doing steps 1, 2 and 4.
My agent wasn't doing that. It was doing steps 3 and 5 on feel. It changed code, ran a pass/fail test, and if the test still failed, it changed something else. A pass/fail test is a terrible number to improve: "failed" after a good change looks exactly like "failed" after a bad one. That's how the agent spent a whole night in Part 7 changing builder code while the colony built exactly zero extensions.
To be clear, nothing about the AI model itself learns here. Its brain stays the same. What my friend did was wrap the same loop around the agent: the bot's code is the thing being changed, and the scorecard is the number.

Two more ideas from machine learning came with it, and both explained things I had already run into:
- You get what you measure. A system that is pushed to improve a number will improve that number, whether or not it's what you meant. My M2 test counted road tiles, and the agent delivered 50 random ones. The fix is to measure several things at once (here: road share, road connections, roads on walls, distance from the spawn), so that gaming one shows up in another.
- Don't let the student grade the exam. The scorecard measures from outside the bot. It reads the game's own event log and never touches the bot's memory, so the bot can't write its own good grades. In machine learning terms, the test data stays separate from what the model can change.
There's also a word for the hard-coded coordinates from Part 7: overfitting. The agent's extensions worked in the test room because their positions were written for the test room. A model that memorizes its practice questions does great on them and badly on anything new. The new rules forbid fixed positions outright, and the linter now flags them.
Tests first: test-driven development
The second habit has a name too: test-driven development, or TDD. The idea is almost backwards on purpose:
- Red. Write a test for the thing you want, before it exists. Run it. It fails, and it must fail. A test that passes before the work is done isn't testing anything.
- Green. Write the smallest amount of code that makes the test pass.
- Clean up. Tidy the code while the test keeps you honest, then start the next test.
I had been doing half of this without knowing the name. Every milestone's acceptance test is written by me, before the agent writes any code, and the agent can't edit it. That's the red step, and it's why the agent can't simply declare victory.
The part that's easy to skip is proving the test fails for the right reason. The new M3 test is a good example of doing that properly. Before the agent touched it, I ran it against the old code, on purpose, to watch it fail:
| What the test checked | Old code (v0.2) |
|---|---|
| Controller level (the test sets it to 3) | 3, so the setup works |
| Tower finished | 0 |
| Extensions finished | 0 of 5 placed |
| Road tiles / on walls / not connected | 95 / 41 / 95 |
| Sources reached by roads | 0 of 2 |
That's a good red. It fails, and every number says exactly why. Since then the agent has been turning those numbers around one by one, which is the green step happening in slow motion.
The other half of the habit is speed. TDD only works when the loop is fast enough that you run it after every small change. My acceptance tests take 13 to 25 minutes, far too slow for that. So the new setup adds a smoke test: 500 ticks, about two minutes, that only checks the colony is alive and error-free, and prints the scorecard. Run it after every change; save the slow acceptance test for when you think the milestone is done.
The scorecard: a number for "better"
The scorecard is the piece that makes both habits work. It rides along with every integration test. On every tick, it asks the game what happened in the room on the previous tick: how much energy was harvested, how much went into upgrading and building (and into what), how much reached the spawn and extensions, which creeps did anything, how busy the spawn was, how much CPU the bot used. Every 100 ticks it also checks the layout: road tiles, roads on walls, whether the roads actually connect, how far extensions sit from the spawn.
At the end of a test it writes a short report next to the previous run's numbers, so every run is compared with the one before. It also adds one line to a history file, so the trend across days is never lost. On top of that comes a per-creep table every 50 ticks (where each creep stood, what it carried, whether it did anything) and the bot's own console output.
Here's part of a real one, from the agent's smoke test after it added a new hauler role. The room can produce at most 20 energy per tick.
| Last 200 ticks | This run | Previous run |
|---|---|---|
| Energy harvested per tick | 3.04 | 2.5 |
| Controller progress per tick | 0.5 | 0.03 |
| Energy delivered to spawn and extensions per tick | 18.1 | 15.61 |
| Spawn idle while its energy was full | 40 % | 6 % |
| Creep ticks spent idle | 33 % | 48 % |
Read it like a doctor's chart. Harvesting improved a little, upgrading a lot, creeps idle less. But the spawn now sits full and unused 40 % of the time, so something in the spawning logic got worse.
And one number is impossible. Over the whole 500 ticks the colony harvested 1,658 energy but delivered 8,862 to the spawn and extensions. You can't deliver five times more than you collect. Something takes energy out of the spawn and puts it back in, over and over. No pass/fail test would ever have found that. The scorecard found it in a two-minute run, and it's now on the agent's to-do list in FEEDBACK.md.
The new rules
The scorecard came with a set of rules that turn the two habits into a routine the agent follows on every run:
| New piece | What it does |
|---|---|
| Run procedure (rule 07) | A checklist for every run. First 15 minutes: read the last gate result, the owner's feedback, its own notes and the scorecard, and write the baseline down. Then the loop: one small change, fast checks, smoke test, read the scorecard, commit with the before and after numbers. Plus stop conditions and a fixed finish routine. |
| Economy playbook (rule 02b) | The Screeps numbers that matter, each tied to its constant in the game's type definitions: 10 energy per tick per source, 2 per WORK part, 1,500-tick creep lives, extension and tower costs. It also lists "things that look right but are wrong", like hard-coded room positions and random moves. |
| Milestone specs | One page per milestone: goal, why now, design notes, acceptance criteria, and which scorecard lines to watch. The agent reads it and can't change it. |
| Linter guardrails | The code checker now warns about positions written as numbers and about Math.random in the bot. |
| Feedback acknowledged | A section in the agent's status file where it has to answer every item in FEEDBACK.md: done in which commit, or why not. |
Rule 07 also changes how the agent commits: every commit message has to carry the scorecard numbers that moved, before and after. The git history becomes a lab notebook.
I checked the playbook's numbers against the game constants, and they're right.
Bringing it in without breaking the walls
My friend sent all of this as a pull request on GitHub: a proposed change to the project that I could review and accept with one click on "Merge".
I didn't click it. Part 2 built a wall around pushing: only a separate push account can send code to GitHub, it only accepts history that adds to what's already there, and every commit has to use an anonymous noreply email. The server had commits that weren't on GitHub yet, so merging on GitHub would have made every later push from the server fail. And the pull request's commit didn't use a noreply address.
So the changes came in the long way, through the front door everything else uses. A small script downloaded the pull request, applied its changes to the project on the server, and committed them as the agent. From there, the gate tests and pushes them like any other work. Once the gate pushes them to GitHub, I'll close the pull request with a note saying where they went.
A few things needed adjusting on the way in:
- Time. With the scorecard measuring every tick, the full test run takes about an hour instead of 25 minutes. The run checklist and the standing task now budget for that, and the time limit for a whole run went from 5 to 6 hours.
- FEEDBACK.md lives outside the project, in a folder the agent can read but not change. The rules now point there.
- Protection. The scorecard, the shared test checks, the smoke test and the milestone specs belong to me, like the acceptance tests. The launcher now puts back the official copies before and after every run, so the agent can read them but never change what counts.
What I learned
- Pass/fail is not a number you can improve. "Still failing" hides whether the last change helped. The scorecard turns "did it work?" into "what moved, and by how much?"
- Measure first, then change one thing. It's the oldest trick in machine learning, and it works just as well when the thing being tuned is code written by an AI.
- The agent optimizes what you measure. Measure several things at once, and measure from outside, where the bot can't touch the numbers.
- Write the test first, and watch it fail. A test that never failed proves nothing. A good failure tells you exactly what's missing.
- Fast feedback beats perfect feedback. A two-minute smoke test after every change is worth more than a 25-minute acceptance test once a night.
- Ask a friend. I'd been staring at this project for a week. Someone with fresh eyes and the right background saw in one evening what I'd been circling around.
Next up: actually running all of this. That turned out to be its own adventure, and it's the next post.