Get notified

Dev Log  · 

Text you can actually read, and a crash that signed its own name

An eighteen-year-old played CropCorp for fifty-four minutes and said the same thing four times in his first three minutes: the text is too small. He was wearing glasses at the time. That single note outranked everything else on the list, because roughly six players in ten will hit it and none of them will phrase it as politely.

The cause was structural rather than cosmetic. This HUD is drawn straight onto a canvas in fixed pixels, which means it does not get bigger as the screen does — it gets smaller, because everything around it grew. So the fix is not a font size, it is a virtual canvas: every panel is now laid out in units that pretend the screen is 1080 tall, and exactly three functions multiply those units into real pixels on the way out. One number controls all of it, that number is derived from the viewport height so the HUD stays the same physical size on any monitor, and the player gets their own multiplier on top of it — a UI SCALE stepper on the front door, 0.75 up to 2.00.

The interesting part was what the photographs caught that the code could not. At 1.6 the hero’s health bar ran underneath the hotbar. At 2.0 the resource strip walked off the right-hand edge of the screen and the countdown printed into the calendar panel. All three are the same bug in different clothes: a panel pinned to a corner reaches the middle of the screen once the canvas is small enough. They are fixed, and they are fixed generally — the corner panels ask the hotbar where it starts, and centred headlines shrink to the room they actually have.

And a smaller, more embarrassing lesson. The first three sets of before-and- after screenshots were taken with the console command that sets the scale, and they were identical, because that command never reaches the setting before the first frame draws. There was nothing wrong with the pictures; they were honest pictures of a change that had not happened. The HUD now prints the scale it actually drew at, every time it changes, and there is a proper command-line switch that works. A tool you cannot check is not a tool.

While the settings were being rebuilt they also learned to persist. Every knob in this game — volume, hold-length, screen effects and now UI scale — was a console variable with no home on disk, so a player who turned the HUD up because they could not read it did it again on every single launch. That is a bad joke to play on an accessibility setting.

The other headline was the crash. The session log’s last act was an engine assertion about the scene viewport being destroyed on the wrong thread, and the line immediately above it said Alt-F4 pressed! — which settles it. Windows turns Alt+F4 into exactly the same message the [X] button sends, so the two are one path, and the fullscreen toggle everyone suspected was never involved. It is a race: the game thread lets go of the viewport while the render thread may still be holding it, and whoever lets go last runs the destructor. That is why it took him fifty-four minutes and sixteen towers to lose it. The window now waits for the render thread to finish before it comes down.

Trying to reproduce it produced a small lesson of its own. Closing the packaged build over and over reported clean exits every time, which felt like useful evidence right up until the packaging step refused to overwrite the game executable: two of those “closed” games were still running, hours later. The packaged launcher hands off to a second process, and the exit code being reported belonged to the launcher. The fix is still the right fix and it is verified to run – but it is not verified against a reproduction, and the difference between those two sentences is the whole point of writing them down.

Then there was the thing he never found. In forty-eight minutes he never once bought a turret upgrade — the biggest purchase in the game — with a to-do line about it on screen, badged NEW, at minute forty-five. Someone told him out loud, and twenty-three seconds later he bought his first; in the four minutes after that he bought twenty-seven. The lesson is not “announce it louder”, it is where: standing at a healthy gun, the game said nothing at all, while it had something to say about every tree in the valley. Now the gun you are looking at says what it costs to deepen it, and any gun you can already afford to deepen wears a gold chevron during the morning, so the field itself tells you where the planks want to go.

Finally, the villagers. A watchdog added last week frees any villager that has not got closer to its work for eighteen seconds; in his session it fired sixty-nine times. Tonight those log lines were turned into a map, and the map answers the question the number could not: the wedges are not everywhere. The three hottest spots and all twenty-seven fence-climbs sit inside the same thousand units of ground — the strip where he built his wall. Villagers steer perfectly well across open farmland and fail where the player has built. That is a much smaller problem than “pathing is broken”, and a much more specific one, and it is written up properly rather than guessed at.

Two more things came out of the night, both from finally running the balance band properly. It has been quoted at three seeds for weeks, and three seeds turn out to be sampling a fat tail: run ten and the median is stable, and it says difficulty did not move at all tonight despite the HUD, the audio, the input map and the dialogue all changing. What did move was the proxy. Buried in the autopilot’s retreat was a rule this project has now broken four times — a safe spot inside the danger radius is a deadlock, not a shelter. A badly hurt hero retreated to the granary, and on a boss day the boss is standing on the granary, so it oscillated there while the thing it was defending was eaten. Fixing that removed one run in ten that had been dying on day two, and the “unwinnable map” everyone would have blamed turned out to be a dance.

And with an honest proxy the band says something very clean about the game. Move the chapter length — which moves when the boss arrives — and the day runs end on moves with him, to the day. At sixteen waves a chapter, nine runs out of ten, on nine different maps, ended on exactly day sixteen, which is the day the boss walks in. That is not a difficulty curve; it is a wall in a fixed place. And the boss is the one thing in this game that does not scale with the day: nine thousand hit points whether he arrives on day six or day sixteen, while the wave he brings with him grows by half. Whether he should scale with the day, the chapter, or with what the player has actually built is a design question, and it is now the most valuable one open.

CropCorp screenshot — Before: the HUD at 1.0, which is what he played
Before: the HUD at 1.0, which is what he played
CropCorp screenshot — After: the new 1.25 default
After: the new 1.25 default
CropCorp screenshot — The same HUD at 1.6
The same HUD at 1.6
CropCorp screenshot — The UI SCALE stepper, beside the volumes
The UI SCALE stepper, beside the volumes
CropCorp screenshot — Where villagers actually get stuck
Where villagers actually get stuck

Recruitment

Get the next entry by email

One email when the Steam page opens, one when there is something playable. Nothing else.