graevy
Wetware Developer
Space Station 14
Is a vehicle for improvised comedy videogame fork of a 2001 atmospherics simulator. It’s like a multiplayer Rimworld, with the complexity of Dwarf Fortress1. Twelve years ago I’d play as an uncannily helpful mime, an alcoholic detective, or usually a micro-managing chief medical officer. Rounds last 1-3 hours, involve 10-100 players, 0-3 admins functioning as dungeon masters, and a subset of players designated as antagonists ranging from xenomorph queen to corporate spy.
The Problem
Ss14 follows its predecessor, ss13, which still exists in a feature-crept, if complete, legacy codebase, collapsing under the 2-decade weight of the hobbyist game developer revolving door. To play ss13, you have to download BYOND, a graveyard of a game client buoyed by ss13 players. While ss13 emerged alongside the popularity of entity-component design, its codebase is largely functional. It is so engine-bottlenecked that the functional player cap sits around 802.
Enter the fully open-source C# rewrite. It has ECS design, its own engine, order-of-magnitude performance increases, etc…I was disappointed I couldn’t contribute to the codebase when I was learning python a few years ago. Things have changed!
Entity-Component-System Conceptual Hurdles
Coming from rust, which splits your 2000s-era-language class
es into inheritance trait
s, data struct
s, and method impl
s, ECS was more approachable. Entities, components, and systems are almost 1:1. Zero experience3 with async programming or event structures was my main hurdle. C# was completely new to me, but that’s not the barrier it used to be with more language exposure4 and LLMs providing the basic syntax. I decided to stay with VSCode to learn fewer new things at the same time, despite Visual Studio’s better C# support5.
It’s an ideal learning experience because I’m familiar enough with the game to know the requirements ahead of time. I remember the little display screens counting down the arrival of the emergency escape shuttle; coding them is purely conceptual. Most others on the project have a similar background, or actively play both versions of the game already. Very little needs to be communicated relative to other projects, which only has a few downsides: sparse documentation, style isn’t rigidly enforced, lone devs prioritize features over fixes, etc.
Timeline
I committed around an hour a day to ss14.
- 6/25: I started seriously looking at the codebase.
- 6/27: I picked an issue and asked a maintainer about some of the code. I’m usually ignored in the help channel and feel like an annoyance, so I stop.
Half-week break
- 7/6:
git log
ing the entitysystem for item reflection tells me that there is an entity in the game that reflects hitscan projectiles. - 7/8?: I understand enough to attach a component to an entity to influence the reflection system. There is a single page of documentation about how to achieve this abstractly. I feel stupid, even though I manage to make an entity reflect a shot.
Another half-week here
- ~7/15: total documentation exhaustion; keyword-searching discord channels becomes my main information source6.
- 7/17: The event system makes sense after I reread a single line from the page: “Basically, we’re telling the game: Whenever a UseInHandEvent is raised on an entity that has the PlaySoundOnUse component, I want you to call my OnUseInHand method.”
One of my days was just learning to squash git commits. git rebase -i HEAD~10
is laser-etched into my hippocampus.
- 7/20?: I write the event handlers that govern reflection without crashing the debug environment for the first time. My solution for multiple separate entities providing the same reflection effect is beautiful.
- 7/21: PR Submitted.
I am grossly overconfident.
- 7/22?: The maintainer who wrote the issue likes my code and makes some value tweaks that introduce div0 errors I documented in the same PR. I immediately realize my solution won’t work because someone is going to set
reflectprob
to 1 even if// DON'T SET TO 1
is in the system and component files. - 7/23: I decide to maintain a map of
entity
:reflectprob
s. After I’m done, I realize this is just a subset of equipment entities outside their designated slots; this is deeply unsafe. I simplify my solution by iterating over the full set after I comprehend the resource-intensity of pressing a single movement key while helping someone in the help channel. People seem annoyed I’m wasting their time on a messy PR. My commit history is a mess and 3 maintainers are breathing down my neck about value tweaks like Office Space. I pick up my second issue. - ~7/25: I understand the codebase enough to help people in the help channel. I commit to leaving nobody unanswered in the help channel7.
- 7/29: I understand the mess of 4 systems that interact8 for my second issue and finish the proof-of-concept.
- 7/30: The issue maintainer merges my first PR. I’m chuffed about it, but clearly generally insecure. A maintainer talks about how welcoming everyone is.
My second (slightly more complex) issue “solved” after 1/4 of the time, my first PR merge9, and the end of July, impart a sense of milestone that compels me to blog about “finally having learned C#, ECS, and the SS14 codebase” or something similarly masturbatory.
both games I have never played ↩︎
ss14 supports around 3x that number at a larger tickrate ↩︎
one python telegram bot aside, ↩︎
major semantic overlap with java ↩︎
I think this was correct, but IDEs aren’t that significant ↩︎
Discord is losing a lot of revenue if they aren’t labbing fine-tuned LLM subscriptions for project servers’ documentations. The search is mediocre and there’s a lot of chaffe; the exact conditions pushing search engines to adopt their own LLMs. ↩︎
actually one of the best ways to learn things. if you already know it, it’s a 30 second explanation. if explaining it doesn’t take 30 seconds, you probably don’t know it. ↩︎
The screen timer object inherits from
signaltimer
, the underlying system controlling machine linking (which I still don’t understand). It needs to trigger when the emergency shuttle is called; there is no event for this, and its logic is located inRoundEndSystem
and notEmergencyShuttleSystem
. I create the unsupported events and leave a note in theEmergencyShuttleSystem
methods thatRoundEndSystem
controls most of the logic; I’m too green to get away with a refactor. ↩︎don’t even try to pretend the contributor tag isn’t a status symbol ↩︎