Grumpy Gamer

Ye Olde Grumpy Gamer Blog. Est. 2004

Sep 8, 2025

Hi there! I’m Elissa, and I’m the other designer on Death by Scrolling and doing a guest post this week. It’s technically my second project with Ron now, and when I’m not designing my own Roguelike (Dungeons of Freeport), or other games such as Deck & Conn, I’m drinking coffee and fighting drop-bears here here in the sunbaked land of Australia. If you want to follow me on social media, I’m on Mastodon primarily at @vampiress@eigenmagic.net and on Bluesky sometimes at @elissablack.com

A confession to make. I started writing this blog post, got a ways in, and then realized there was a critical problem with it: almost nobody reading the post would have any idea just how Death by Scrolling actually plays, so it’d probably wash right on over them producing a painted-on polite smile.

So here I am, back at the start, suddenly finding myself giving the elevator pitch and basic game play description of the game. It’s also easier now that some game play footage as been released.

Death by Scrolling is a vertically scrolling action-roguelike game.

TesterTron3000 playing Death by Scrolling

You select one of several different characters and progress through increasingly long and densely populated levels full of villainous nasties, treasures, gold coins, traps, collectables, and simple puzzles. At the end of each level you get a brief moment of respite at a camp where you can pick up and buy powerups, take or turn in quests, and go make another tea in the kitchen before returning to your computer before venturing ever-further into the fantastical worlds of Purgatory.

At first glance, the game doesn’t look like a Roguelike. Even in the most loose definition of the term - it’s vertically scrolling and action-driven. If anything, it seems to have more in common with something like River Raid or 1942 than a dungeon crawler. But to me, where it starts to show its lineage is when it comes to do level design in the game.

In terms of my work on the project, I’m designing & writing the game with Ron, but probably the majority of my time is spent doing the level design. As such, I felt like that was a good place to start when writing a guest blog post - I could focus on one of the only things I actually do more than Ron on the game.

In the most superficial sense, the game’s levels are top-down 2d level prefabs designed in Tiled, a very useful open-source tile-based map editor, and stitched together at run-time by the game.

The individual pieces (we refer to them as prefabs) each have different bits of meta data attached to them such as their Land (or biome), what kind of prefab they are, what other prefabs they can attach to and what the probability is that they might appear, what mobs can spawn, etc.

For instance, a very simple, short bit of green grass with a small hill and some simple decorations might be pretty common, and appear regardless of what level you’re at, where-as a more complex prefab featuring a maze may be rare, limited to higher levels only, and might be flagged to only appear once in a level.

Using this metadata we can set it up so that early levels are shorter, simpler, and contain fewer ‘puzzle’ prefabs (what we call the more complex prefabs that have optional mazes, gates or enemy ambushes in them).

If a new player starts on the first level of a run (which is always in the grassy Land), the prefabs that get chosen to assemble the level are different to, say, the 15th level for a player who’s unlocked lots of upgrades and is currently in the Swamp land.

So, to create a single Land means designing the aesthetics, the enemies that occupy its levels, the basic gameplay style, and then, finally, a good hundred or more prefabs that make it up.

Doing those first bits isn’t easy, but it isn’t as time consuming as the weeks and months spent making up all the prefabs. This process usually has three steps.

First step: drinking coffee.

Second step: coming up with basic shapes and paths. This I tend to do in batches, doodling in a notepad or on my tablet, sometimes at a cafe, on a train, or really anywhere that isn’t my work desk (for a change).

With enough of these basic shapes figured out in varying levels of detail, it’s then time for-

Third step: spend days in Tiled, creating first at first the basic layouts, then adding more aesthetic detail.

I usually alternate between doing all three of these critical steps - a few hours in Tiled, then going for a walk to clear my head, get a coffee, and come up with some more prefab ideas.

It’s this design process that to me really drives home how much the game is a Roguelike (or is at least related to them - ask two fans of roguelikes what makes something a roguelike and you will get five different answers).

Just like designing most any other Rogue-adjacent game, it also means that staring at prefabs and their metadata until your eyes go square won’t truly tell if what you’ve built plays well. Great ideas on paper don’t always work. Or maybe they do, but with a few powerups they become too easy (or too hard).

In Tiled, shown above, we have data layers that exist along with aesthetic ones. Different data types (each a unique colour for easy recognition) can be painted on that layer to allow the prefab designer to, for instance, stop a player jumping onto that square, stop power-ups from randomly spawning there, or forcing a square to be non-passible.

Which means built into this prefab design cycle is also a crap-ton of testing. From almost the very start we’ve had regular QA and play-testing done at least a few hours a week. What puzzles are too hard when you’re moving at speed? What other forms end up being fun and should be put into more prefabs?

Even the simple act of adding more prefabs can unbalance the game if we aren’t careful. Add 20 more prefabs to a given Land, and suddenly those rare ones you made might occur less frequently than you’d like.

In the end, it’s meant that designing this game has been a massively cyclic process. Adding features, prefabs, or puzzles, tinkering with them, experimenting, and even removing some if they turn out to have been better left on that scrap of napkin at the cafe down the road from my place.

Read Comments
Sep 6, 2025

“But? Wait?” I can hear you saying, “Isn’t grumpygamer.com a static site built by Hugo? What dark magic did you use to get comments on the static site?”

No dark magic. But it does involve a small php script.

You can embed php in a hugo page and since grumpygamer.com is hosted on my server and it’s running php it wasn’t that hard.

No tricky javascript and since it’s all hosted by me, no privacy issues. All your comments stay on my server and don’t feed Big Comment.

Comments are stored in flat files so no pesky SQL databases. It only took me about a day, so all in all not bad.

I may regret this.

I’m only turning on comments for future posts.

Be nice!

P.S. I will post the code and a small guide in a few days, so you too can invite the masses to critic and criticize your every word. Good times.

Read Comments
Sep 3, 2025

Have I mentioned that you should Wish List Death by Scrolling now, before you finish reading this?

Here is the code the runs TesterTron3000 in Death by Scrolling.

There is some code not listed that does set up, but the following runs the level.

It’s written in Dinky, a custom language I wrote for Delores based on what we used for Thimbleweeed Park and then used in Return to Monkey Island.

TesterTron3000 is as dumb as a box of rocks, but in some ways that’s what makes it fun to watch.

Before we get into code, here is another sample run.

It’s not the best code I’ve written but far from the worst and it gets the job done. TesterTron3000 has run for over 48 hours and not found a serious bug, so I’m happy.

Source code follows, you’ve been warned…

 1function runLevel() {
 2	local last_pos
 3	local reset_time = gametime()+MINUTES(3)  // Reset if level took longer than 3 minutes
 4	local dest
 5	do {
 6		if (PLAYER?.dead) return
 7		if (gametime() > reset_time) return
 8		local pos
 9		if (ROOT(in_camp)) {
10			pos = MAP?.start
11			dest = "start"
12		} else {
13			local targets
14			targets = null
15			if (PLAYER.max_health-PLAYER.health > 1) {
16				print("Looking for heart")
17				targets = entityFindSortedForwardEntities(PLAYER, point(0,1), 10, 360, TYPE_HEART)
18				dest = "heart"
19			}
20			if (!sizeof(targets)) {
21				print("Looking for powerup")
22				targets = entityFindSortedForwardEntities(PLAYER, point(0,1), 10, 360, TYPE_POWERUP)
23				dest = "powerup"
24			}
25			if (sizeof(targets)) {
26				local target = targets[0]
27				pos = entityPos(target)
28				local dist = pos?.y - cameraAt().y
29				if (dist < -6) { // Don't grab powerups near the bottom
30					pos = point(random(5,mapSize(MAP).x-5), entityPos(PLAYER).y+random(5,20))
31					dest = "pos"
32				}
33			} else {
34				pos = point(random(5,mapSize(MAP).x-5), entityPos(PLAYER).y+random(5,20))
35				dest = "pos"
36			}
37		}
38		printf("TesterTron3000 moving to %@ (%@)", pos, dest)
39		dest = pos
40		entityPathTo(PLAYER, dest, 9999)
41		breaktime(0.5) // Need time to finish pathing (happens on a real thread)
42		local bail_time = gametime()+SECONDS(8)  // Seconds to path before changing location
43		while(isPathing(PLAYER)) {
44			if (gametime() > reset_time) return // Player probably stuck somewhere
45			if (gametime() > bail_time) break // Took too long on path
46			if (!entityPos(PLAYER)) return // Something is wrong
47			if (!MAP?.end) return // No end pos, something is wrong
48			if (PLAYER?.dead) return // We died
49			if (entityPos(PLAYER).y >= MAP.end) return // Got to end camp
50			local dist = dest.y - cameraAt().y
51			if (dist < -6) break // Too close to the bottom
52			ROOT(took_step)++ // So powerups will tick
53			PLAYER.last_move_vec <- dirToVec(entityFacing(PLAYER))
54			if (PLAYER?.range_image) { // Spin targeting range around
55				imageRotate(PLAYER?.range_image, angle(point(0,0), PLAYER?.last_move_vec))
56			}
57			if (entityPos(PLAYER) == last_pos) break  // Didn't move, something is wrong
58		 	last_pos = entityPos(PLAYER)
59			breaktime(0.1)
60		}
61		// Choose new destination
62	}
63}
Read Comments
Aug 31, 2025

I first created TesterTron3000 during Thimbleweed Park (hence the name). It was a simple automated tester that randomly clicked on the screen. It couldn’t play the game because it has no knowledge of inventory or puzzles. It did find the odd errors, but was of little real value.

Fast forward to the futuristic year of 2025 and I’m working on Death by Scrolling and need a new automated game play tester.

Death by Scrolling, not being an adventure game, comes along and I need a whole new program to test with, but I like the name so I keep that.

TesterTron3000 is pretty simple, it just runs through the level, looks for power-ups and if its health is low, it looks for hearts. It’s not rocket science. I could make it a lot smarter, but what I really need is tool that stress tests the game so smarts of low on the list.

I can leave it running overnight and it plays thousands of levels and in the morning I see if any errors occurred or if there are memory leaks.

None so far.

Its been a great tool for consoles because we can only do limited testing before sending it to outside testers due to limited dev kits1, so running TesterTron3000 on it for 24 hours is good piece of mind.

There are little animation glitches because it’s not running through the normal controller code and I’ve spotted some missing sfx.

TesterTron3000 is written 100% in Dinky and only about 100 lines of code.

I might ship it with the final game as an attract mode, but it’s kind of buggy, has bad path finding, and really stupid so I worry players would fixate on what it’s not doing right. It’s not a tool for playing the game with any degree is skill, it’s a stress tester and a dev tool.

But, it’s fun to watch.


  1. Something a lot of people don’t know is for consoles you need special dev kits to test with, it’s not like the PC (or even the SteamDeck) where you can use any device. You have to buy (often very expensive) special dev kits, even just to test. It’s really annoying. ↩︎

Aug 13, 2025

I was having a discussion with someone on Mastodon about unit testing games and how it is a next to impossible job. Once clarified, I think we saw eye-to-eye on it, but I do hear about it a lot, mostly from programmers that don’t live in game dev.

Testing games is hard. So much of what fails during testing is due to random behavior by the player. Them doing something you didn’t anticipate.

I break testing down to three groups.

1 - Unit testing

This is what I hear about the most and how this would be a good way to test games. It is not. Unit testing will test code components of your game, but not the game. If you have a sorting routine, this will test that, but it has little effect on testing your game.

I do unit testing, mostly on the engine commands. This get run once a week or when I add a new feature. It’s testing that all the command or functions return correct values, but has little to do with “testing the game”.

It’s also something that would be very hard to run from the build process since the entire engine needs to start up. This is why I run it by hand every so often. If it’s not being run in a real engine environment, it’s not accurate.

2 - Automation

I’ve been using automation since Thimbleweed Park.

I called it TesterTron 3000 it ran through the game and randomly simulated clicks and tried to follow some logic. It found a few things, mostly bugs that would only happen if you clicked very fast.

It was fun to watch and gave mostly peace of mind testing. It often broke because we changed the games logic and it could no longer follow along. If I had a team that did nothing but keep TesterTron 3000 working, it would be more useful but given the limited resources of an indie team, I’m not sure it would have been worth it.

I have something similar in Death By Scrolling, it runs through the levels and randomly picks up things and try to attack enemies.

I could have it follow a set sequence of key strokes, but then it’s only testing what you know works, not the goofy stuff that real bugs are make of. I’ve run into programmers that build something like this and at first it feels good but as the game changes it falls apart and doesn’t get used much after that.

Maybe if you had a simple puzzle game, this might be useful.

TesterTron 3000 a good stress tester and it needs to run overnight to be truly useful.

3 - Human Tester

The most important testing we do is with testers who play the game all day long. They look at the Git history and see what has changed and beat on that. 99% of our bugs are found this way and I can’t emphasis enough how important it is.

Players do odd things that automation never will.

While it is important to test your own code, you will never find the “good” bugs. Programmers are lousy testers because they test what they know, you need to be testing what you don’t know. Be afraid if management that says they don’t need testers because the programmer should test their own code.

I grew up in a world where a bug meant you have to remake a million floppy disks and it would take months to get the change out to players, if they ever got it.

Jul 28, 2025

We just got Death By Scrolling running on the Switch. We had to do some optimization because we were doing stupid stuff with rendering the tile map. Modern PC machines are so fast these days that you can do a lot of stupid stuff and it doesn’t matter. Back in the day (queue angry old man) I had to count cpu cycles and every byte of memory was precious. Nowadays memory is basically infinite.

But it’s different for consoles, they do have a limited amount of memory and you do have to pay attention to performance.

One thing that really bothers me when I play Switch games ported from PC or other consoles is the lack of care over font size. Things that look good on a big monitor or TV are unreadable on the Switch (and the Steamdeck).

We’ve taken great care to make everything big enough to be readable on handhelds, but it’s a real pain. As a designer you want to cram enough information on each screen, especially for RPG-ish games.

People get used to html/css rendering and how good it is at flowing to fill space and around images. Games often don’t have text rendering engines that are that complex (using a html/css engine internally is overkill).

One of the big upgrades I want to do to my engine is better text flow rendering, it will never be as good as html/css in the browser, but it could be a lot better/easier. It’s one of the things I’m envious of Godot.

Jul 17, 2025

If you haven’t read my previous post about Death By Scrolling way back in February, I suggest you do.

Of course this is my lazy way of doing the 2nd promised blog post for Death By Scrolling.

In all fairness, I started to write it and it seem awfully familiar so I went back and checked and sure enough I had already written about it.

But, I’ll do another real post…

I asked for beta testers on Mastodon and got close to 300 sign-ups. I didn’t want to invite everyone all at once. There is an old saying that you can only make a first impression once.

Every time I make a new beta version I invite 25 more people.

Couple of stats:

About 25% of the people never redeem the steam key. Or they redeem it weeks later. This is a little surprising, but maybe it shouldn’t be. People are busy.

Of the people who did redeem the key a third play the game once or twice and never again. This is not surprising. Death By Scrolling is a rogue-like and you die a lot. I do mean a lot, it’s right in the title. Some people do not like this type of game, and I’m OK with that.

Maybe half the people who play the game never visit the Discord. We can get only so much info from analytics. Having a conversation about what you like and don’t like is very helpful. Again, this isn’t too unexpected.

The players that do play more than a few times play a lot and that is good to see. It’s nice to see strategies emerge that we, as the designers, didn’t think of. That is always a good sign.

This is the first time I’ve done large-ish beta test for one of my games and it’s been fascinating and very insightful.

I’m about to invite the next group of 25 testers. If you’re among this group, please visit the Discord.

– Ron

Jul 8, 2025

I miss the Kickstarter days of Thimbleweed Park where each week I would write a blog post about how things were going and we’d to a podcast. If we were doing Thimbleweed Park today we’d have a video podcast on YouTube.

I watch a lot of YouTube videos from indie game devs where they document everything they are doing on their game with a fancy video and my first thought is: “Where do you find the time to make this”. I barely have enough time to work on my game.

Now that Death By Scrolling is getting close to releasing, I really should start blogging again about the game. There are a lot of interesting things happening and there is no reason I should keep those to myself.

I have found that as I get older I enjoy pontificating less and less. I’ve stopped doing talks, doing interviews and podcasts. I really enjoy Tim Cain YouTube channel but I could never do that. It’s not that I don’t have anything to say, it’s that I have become self-conscious that I’m just pontificating for no reason and don’t have anything deep and interesting to say.

Now that I’ve got you all primed and ready, starting next week you can plan your day around the Death By Scrolling blog update.

Or maybe I’ll delete this post like it never happened.

^^^ this

Apr 14, 2025

When I build my game for testing, it’s a completely automated process from the command line.

I type pub.sh --test on the command line and a long chain of events is started.

The script pushes to git, which starts the cloud based CI machine (Azure) building the Mac, Windows and Linux executables.

When they are done, my local script is notified and they are download locally.

Game files are added and they are signed and packaged up.

The script then logs into Steam and uploads the game.

A change list is built from the git log and uploaded to a private website.

When that is complete, a message is sent to Discord to notify testers that the build is done.

This is all done with no interaction on my part.

It makes doing test builds quick, easy, and painless. But more important is there is no human interaction, so there is less chance of a mistake being made.

To upload to Steam you need to authenticate, which usually involves getting an email and typing in a code or doing the same with their authentication app. It’s not very conducive to automated builds.

Fortunately Steam has an process where you authenticate once and then pass a token from the command line and that logs you in without a password. Wonderful.

This worked fine for what seem like years, then on Jan 1, 2025 I got a error saying my token was expired. OK, makes some sense.

I re-ran the process to get a new token and everything was fine for a day or so, then it failed again saying the token was expired. Then it happened again. And again.

Frustrated, I reached out to the friend who works at Valve and was put in contact with a programmer there. He watched the back-end and said everything was fine for the first few auths and then the old expired token as submitted. Rebuilding the token worked for a while and then the old token was submitted again.

We began to suspect the token file on my machine was being overwritten but I couldn’t see it happening and didn’t know how it was happening. It not like the whole directory was being restored from a backup, it was just that one file sitting in an obscure Steam folder.

We tried everything and never solved the problem.

The solution I finally arrive at – and has worked fine for the past three months – was to get an auth token and then save that file away. Just before my script uploads to Steam it copies that good file over the current one and uploads. This is what Steam recommends for full CI builds, but until Jan 1, I never had to do this locally.

I’m still mystified who is overwriting that file but I guess it’s moot now.