So, one of my favourite games lately, Timberborn, has released its 1.0 version and finally left early access.
For those who don't know it, Timberborn is a city-building strategy game. In this game, the player takes over the role of a ruler of a beaver colony - after humans (or hoomans, as they're called in-game) vanished from the face of the planet, the beavers took over. There's resource gathering, taking care of your beavers' different needs, building complex water-based power networks, factories, transportation, etc., etc.
Or, a database, in my case.
There are 3D water physics, different seasons such as droughts, where you need to retain water and bad tides, where new water entering the map is polluted and harmful to beavers.
Since the initial release in 2021, many features have been added.
And the latest update broke the game for me a bit. Because they added automation features. These automation features include various sensors for detecting bad tides, water flow, etc. They even added logic gates and HTTP levers.
Oh no
Oh no indeed. I had to play around with these. This is what they look like in-game:
As you can see, they have a name (in this case “HTTP Lever 1”), a “Switch-on URL” and a “Switch-off URL”.
The idea behind these is that streamers can hook these up to other services, for example, Twitch webhooks. If someone leaves a like, fireworks go off, stuff like that.
But: Who says you can only use one of them?
Do you see where I'm going with this?
Oh no
Oh yes. The game can work with around 1000 of them for some time. Windows said it "had some issues with the system" twice while I was experimenting, so from the very start, this isn't very practical. It doesn't have to be, though.
For every lever, there is one endpoint that turns it on and one that turns it off. No batch processing, sadly, but perhaps there's a mod. Another endpoint returns the state of each lever on the current map. The data looks kind of like this:
[
{
"name": "HTTP Lever 829",
"state": false,
"springReturn": false
},
{
"name": "HTTP Lever 154",
"state": true,
"springReturn": false
},
{
"name": "HTTP Lever 839",
"state": false,
"springReturn": false
},
{
"name": "HTTP Lever 164",
"state": true,
"springReturn": false
}
]
With two states, such an HTTP Lever can be thought of as a single bit.
All that is needed is a way to read from and write to them.
The Timberborn web interface: How it works
The idea is that we’re going to convert some user input to JSON, convert that to binary using ASCII encoding and flip each bit one by one. When reading, we read all the lever states at once, rearrange them into a bit sequence, split it into 8-bit chunks, decode them back into characters, and parse the resulting JSON. And voila: A read/write data storage.
Let’s start with a little HTML:
<form method="POST" id="form">
<div>
<input type="text" id="title">
</div>
<div>
<textarea id="text"></textarea>
</div>
<button type="submit">Store in Timberborn</button>
</form>
<button type="button" id="load">
Load data from Timberborn
</button>
Now comes the fun part. We start with a bit of scaffolding JS:
const title = document.querySelector('#title')
const text = document.querySelector('#text')
const form = document.querySelector('#form')
const load = document.querySelector('#load')
const chunkSize = 8 // We need this to later split the bits
const numberOfLevers = 1000 // This needs to be the exact number of levers in-game, otherwise it won't work reliably.
Next, we listen to a form submit and build a JSON string from the two fields:
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = {
title: title.value,
text: text.value,
}
const json = JSON.stringify(data)
// ...
})
Once we have this, we can transform this into a series of binary strings:
form.addEventListener('submit', async (event) => {
// ...
const json = JSON.stringify(data)
const asciiEncoded = json.split('')
.map(c => c.charCodeAt(0))
const binary = asciiEncoded.map(
num => num.toString(2).padStart(chunkSize, '0')
)
// ...
})
This gives us an array of 8-bit long strings with 0s and 1s:
We next need to join these up and build the large final bit string. We translate those bits into booleans and then API URLs, which we can then call with fetch:
form.addEventListener('submit', async (event) => {
// ...
const bits = binary.join('')
// So there's no leftover data in the registry
// at the end of the current data
.padEnd(numberOfLevers, '0')
.split('')
.map(b => b === '1')
const allUrls = bits.map((bit, key) =>
`http://localhost:8080/api/switch-${bit ? 'on' : 'off'}/HTTP Lever ${key + 1}`
)
await Promise.all(allUrls.map(url => fetch(url)))
console.log('done!')
})
When saving, this will trigger a total of 1000 HTTP requests to the game's endpoint. No wonder it doesn't like it.
But: It works!
(There's a gif, it may take a few seconds to load...)
Reading data from Timberborn
Next, we need to read data. As we've seen in the example, the lever states can be fetched all at once, but their order is all scrambled up. We can fix that by first loading everything and then sorting. We then translate everything back into a bit string, chunk that up and decode that into ASCII again:
load.addEventListener('click', async () => {
const response = await fetch('http://localhost:8080/api/levers')
const json = await response.json()
const sorted = json.sort((a, b) => {
const aNumber = Number(a.name.replace('HTTP Lever ', ''))
const bNumber = Number(b.name.replace('HTTP Lever ', ''))
return aNumber - bNumber
})
const bitString = sorted.map(l => l.state ? '1' : '0')
const chunks = []
for (let i = 0; i < bitString.length; i += chunkSize) {
chunks.push(bitString.slice(i, i + chunkSize).join(''))
}
const numbers = chunks.map(c => Number.parseInt(c, 2))
// We filter out everything that's only 0s, because that's likely garbage-data
.filter(n => n > 0)
const letters = numbers.map(n => String.fromCharCode(n))
const data = JSON.parse(letters.join(''))
title.value = data.title
text.value = data.text
})
And that's it!
Technically, this can be considered a cloud storage now. Since Steam uploads save games to the cloud and Timberborn treats HTTP levers as stateful (i.e. it saves their state when saving the game), everything's persistent.
Whoever manages to get more than a measly kilobit of data into Timberborn, please hit me up, I'm pretty certain we can make a Drupal database adapter for this somehow.
(PS: Sorry, not sorry for the AI image. I had to try it eventually!)
(PPS: This post is not sponsored. But Timberborn devs, if you read this, I'm up for way more shenanigans using the automation features :D)
I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a ❤️! I write tech articles in my free time and like to drink coffee every once in a while.
If you want to support my efforts, you can offer me a coffee ☕! You can also support me directly via Paypal! Or follow me on Bluesky 🦋!





Top comments (18)
I finally understand what data lakes are!
Can I branch this DB?
How good is the logging? I assume I can just run a command line tail?
How many bytes does it take?
You knaw what, I will see my self out.
I was on the bus when I read this, people tend to look puzzled when you burst out laughing on public transport :D If you have any more of these, go ahead, comment sections under my posts are usually pun-friendly ;)
Haha, good to know bud! (Also loved it, haven't played Timberborn in over a year so a nice reminder to pick it up again!)
The rest I cam up with were weaker to be fair.
Can I deploy this with terraform etc.
I think I did dam well the first time to be fair :-P
Aah, I love the terraform one, though! :D
I'm too long in the tooth to understand any of it.
Using a game state as a queryable data store is a legitimately clever angle - game saves are already structured state that changes over time, which is basically what a database is. The beaver colony resource model probably has cleaner relational semantics than some production databases I have worked with.
It's a lot of overhead, though, and as mentioned, I ran into two bluescreens before I got it working. I'm pretty sure one could use the water physics of the game to store more data and make this halfway usable, but the engineering involved would be way over the top.
two BSODs is the real commit cost. that water physics angle sounds like a great post even if it never ships - the gap between feasible and sensible is where the best dev stories live.
I have the suspicion that the BSODs were actually caused by the amount of end points the game had to create once I clicked on the "Turn on API" button. The game does offer memory units that store single bits and can operate in various modes, so theoretically one could use a single HTTP lever as the input bit, a counter made up of a handful of those memory units and a few logic gates (I implemented a binary counter as a starting exercise, which was surprisingly easy) and a few hundred more memory units as the actual memory to have it act like a bit stack that could be addressed. It's a bit over-simplified, but RAM doesn't work all that differently, actually. It would complicate both reading and writing a lot, but I'm pretty sure it would be less memory intensive and scale a lot better. Downside is that the writing requests from the browser then need to be well-ordered, whereas now they can all be fired simultaneously and the addressing is done via the HTTP lever's names. Alternatively, one could use something like 21 input bits plus a few more for the address, allowing to store individual characters in memory "cells", so the HTTP requests can be fired in packets, improving performance.
Just thinking aloud here, but in theory, once that's implemented, we're not far off to just building a CPU. Add a ROM storage plus a rudimentary 2-colour-display and you've got a full-blown programmable computer. All the parts are there, it's only if the game crashes before the first frame renders or not lol
that tracks - spinning up hundreds of endpoints at once is the kind of burst the kernel isn't designed for
This is one of those ideas that sounds completely wrong but somehow makes perfect sense
Turning HTTP levers into bits is actually kind of elegant in a chaotic way
Also 1000 requests just to store a string is peak “it works, don’t touch it” engineering
The 1000 HTTP levers were, to me, actually the path of least resistance. I could imagine a setup where only a handful of input bits are necessary to have a fully operational multi-kilobit memory, but the downside would be that the sent packets would need to be well-ordered (i.e. one byte or whatever after the other, depending on packet size), because otherwise data gets mangled. That would, in turn, have a performance impact and one would need to implement addressing with in-game logic, which is a whole different can of worms. The HTTP lever's names took away the need for addressing, since the bits are always sortable, no matter the order in which they're written or read.
I love the chaotic "mad scientist" part of engineering, where creativity and sheer stubbornness are the key to innovation, even if the innovation only makes people laugh!
This is so sick. I love Timberborn, but haven't gotten to play around with 1.0 yet. This post single handedly is gonna move it up my to play list.
Thank you so much! Can only recommend it, I sunk dozens of hours into this gem already!
This is the kind of unhinged engineering I live for. Next step: build a REST API on top of it and deploy to production. Your database is now beaver-powered. The uptime depends on whether the beavers have enough water.
It needs a running instance of the game, so to deploy it, you'd probably need an extra steam account per instance, but it's very well possible to do this! The good thing is, that the beavers don't need to actually do anything once it's built, so they may enjoy their free time while the database does its thing.
My only question unanswered.. how is the QoL for your beavers in your database world? I was mesmerized when redstone released in Minecraft and people quickly got to work creating logic systems and creating working computers, and I am getting the same feeling here. I hadn't checked out this game since it released, but may be time to do so!
The little fellas are fine, they've got plenty of food, water and shelter. Although that's only the bare necessities, it's enough to keep a stable population. I used the sandbox mod, otherwise this would've taken months to build.
Timberborn has one significant advantage over Redstone: Logic gates and latches are already built in. I originally wanted to build a binary adder, but that took me 5 minutes, since AND and XOR are there already, you only need to hook them up properly. But that means computers are theoretically possible. A small 2-colour display could be achieved with lamps, so playing Space Invaders in Timberborn should be doable!