So I made a game.
Well, “game” in the sense that it does not have a jump button, there are no loot boxes, and nobody is 360 no-scoping anyone from a rooftop. But it does let you make decisions, ruin lives, get yourself atomized in space, and discover secret endings if you’re stubborn enough.
So yes. A game. Kinda.
It is called Prometheus, and it is a cyberpunk terminal infiltration game built with Python and Textual. You play as a hacker sneaking through military ships, prison transports, dreadnoughts, blacksites, and weird off-grid archives trying to steal the kind of information that usually starts wars.
Which, if we’re being honest, is exactly the sort of thing my brain thinks is a relaxing weekend project.
Why Make This At All?
I have always liked games where the decision itself feels like the mechanic.
Not just “press X to hit goblin” but “press X and now you have to live with what you just did”(also my reaction time sucks lmao). That very specific type of dread is beautiful. And one of the things that scratched that part of my brain very hard was Bandersnatch from Black Mirror.1
That thing was weird, manipulative, funny, a little cruel, and very aware that the player being involved is the actual point. It wasn’t just branching for the sake of branching. It made choice feel visible. Sometimes ridiculous. Sometimes fake. Sometimes devastating. ( Ps. tried taking LSD in order to fully immerse myself in the making of the game but seems like my supplier is locked up)
And I really liked that.
I didn’t want to copy Bandersnatch directly because then I’d just end up making “Netflix but with less budget and way more Python errors.” What I wanted was the feeling:
- choices that matter
- routes that loop back and cross over
- endings that feel earned, cursed, or both
- a system that quietly remembers what you’ve touched
- a structure that makes replaying part of the game instead of a chore
Also I wanted an excuse to make a terminal UI look dramatic, and Textual gave me exactly enough power to become annoying with it.
The Pitch
The elevator pitch for Prometheus is:
What if Bandersnatch got really into sci-fi espionage, military corruption, prison ships, and hacker nonsense?
Also you gotta cut me some slack because I wanted to start out as Cyberpunk edge runners but that was too ambitious.
You start on a skiff in space with a stack of bad options and some slightly worse options. From there, you can infiltrate patrol cruisers, prison ships, dreadnoughts, raid smugglers, find ghost frequencies, awaken weird systems, and eventually stumble into the “oh wow this got bigger than I thought” layer of the story.
There are 40 endings, and the game is built so that replaying is not just expected but basically the whole joke.
Some endings are clean wins. Some are “technically alive, spiritually cooked.” Some are secret. One is the real “ohhh so this is what the whole thing was building toward” ending.
Which is my favorite kind of nonsense. Anybody who knows me knows I love things that don’t make sense, FUCK LOGIC (not the rapper, I ain’t got beef there. you get my point). Life’s absurd , why not play with the absurd.
The Stack: Very Fancy Terminal Wizardry
This project is built with: (call me Gandalf or not… )
- Python
- Textual
- Rich
- JSON story files
- vibes, mostly
No game engine. No Unity. No Godot. No particles exploding all over your GPU. Just a terminal UI pretending to be cooler than it has any right to be.
At a high level it looks like this:
graph TD
A[Story JSON] --> B[Loader]
B --> C[Game Loop]
C --> D[Game State]
C --> E[Global Progress]
C --> F[Random Events]
C --> G[UI Panels]
D --> H[Save Slots]
E --> I[Ending Tracker]
And honestly, I love this setup because it stays understandable.
The story is data. The engine interprets the data. The UI renders the consequences. And I get to pretend I am making a little narrative operating system. (Jerk me a bit here yeah..)
The Core Design Problem
Branching games sound easy until you actually try to make one.
At first you think:
“I’ll just write a story and add some choices.”
Then like ten minutes later you’re staring at a graph that looks like a conspiracy board made by a guy who hasn’t slept in three days.
Dev notes this is a side quest please feel free to skip :
Okay it took me like 2 years to finish this just cause of the story line and how much adhd I got jump starting other project not to finish later on, but main thing being writing the ending I kept writing bullet points on my common place book here and now then when the time got to making the game I would think oh yeah not this but that and so on, I am actually baffled that it has been 2 whole fucking years too cause in the back of my head its been like maybe 6 month, time flies man, also another reason being capitalism I have to go around pleasing the overlords so they don’t get bored of my existence what am I but a mere jester in the court of atlas shruggers (shruggalos) but hey back to the main point now. * checkpoint saved lol
Because branching stories do this evil little thing where every new branch multiplies your future work.
If a node has an average branching factor of \(b\) and the average meaningful depth is \(d\) then the naive explosion looks like:
\[N \approx b^d\]Which is very fun mathematically and very rude when you are the one writing all the text. (bro if you asked me the same thing on a math test I’d prolly fumble lol)
Even a modest tree like:
\[3^6 = 729\]is enough to make you reconsider every life choice that led to opening your editor. (blessed be claude now, am i right guys? AM I RIGHT?…. even though I’m not a dario fan the case being …. will get to it some other time)
So the actual trick is not “make infinite branches.”
The trick is:
- branch where it matters
- fold paths back together where it makes sense
- gate special outcomes behind state
- hide enough weird stuff to reward replay
That was the real structure behind Prometheus.
How the Story Is Structured
The game story lives in JSON.(everything lives in JSON lmfaoo) Every node has:
- an
id - a
location - story text
- a list of choices
- optional effects
- optional ending metadata
Each choice can also carry:
- risk level
- stat effects
- inventory changes
- flags
- conditions for whether it should even appear
So instead of hardcoding every route in Python, I made the Python act more like a story interpreter.
That means the engine can load nodes like:
{
"id": "ship_select",
"location": "Starfield // Theater Overview",
"text": "Three military hulls dominate the tac-grid...",
"choices": [
{
"id": "board_alecto",
"text": "Infiltrate CRS Alecto...",
"next_node": "alecto_entry",
"risk": "warning"
}
]
}
Which is way nicer than burying story structure inside giant Python conditionals and then crying later. (which I did for the first half….)
The Game Logic: Small Engine, Big Attitude
The actual engine is pretty compact, which I like. The GameLoop mainly does four things:
- figure out what node you’re currently in
- filter choices based on conditions
- apply the effects of whatever you chose
- enter the next node and see if that triggered an ending
That flow looks like this:
graph TD
A[Current Node] --> B[Filter Choices]
B --> C[Player Picks Choice]
C --> D[Apply Effects]
D --> E[Move to Next Node]
E --> F[Apply Node Effects]
F --> G{Ending?}
G -->|No| H[Render New Choices]
G -->|Yes| I[Show Ending]
The nice part is that story complexity is mostly expressed as data, while the engine remains basically one polite little machine whose whole job is saying:
“Alright, let’s see what terrible decision you made this time.” (aight hear me out… this is just life)
Stats, Flags, Inventory, and Other Ways to Ruin Your Own Future
Prometheus tracks state through a few simple pieces:
- stats like
stealth,intelligence,trace, andreputation - inventory for things like the
janus token,quantum key, orghost shard - flags for narrative facts like whether you contacted a ghost signal or pissed off a rival hacker
- visit counts so the game knows what you’ve seen before
The important part is that choices are not only “go left” and “go right.” They can reshape what options even exist later.
For example, a late-game route might require:
- a specific item
- a minimum stealth score
- a maximum trace score
- multiple flags being set already
Formally, a choice is available only if all its requirements pass:
\[\text{available} = \Big(\bigwedge_{i \in I} i \in \text{inventory}\Big) \land \Big(\bigwedge_{f \in F_{all}} f \in \text{flags}\Big) \land \Big(\bigvee_{f \in F_{any}} f \in \text{flags}\Big) \land \Big(\forall s,\; \text{min}_s \leq value_s \leq \text{max}_s\Big)\]Which is a very serious looking formula for something that often boils down to:
“You may now access the weird secret thing because you stole enough cursed space objects.” (I know you know that I know you love how that sounds)
The True Ending Is Basically a Heist Receipt
My favorite part of the whole system is how the true ending works.
I did not want the best ending to be something you get by accident after button mashing your way through six menus and a lucky roll.(even though that there is true dopamine cause its basically gambling) I wanted it to feel like a layered payoff. Something that says:
“No no, you weren’t just wandering around. You were assembling a possibility.”
So the true ending, Prometheus Unbound, is gated behind a pile of requirements:
- the
ghost shard - the
quantum key - the
janus token - the
janus root phrase - enough
stealth - enough
intelligence - not too much
trace - the right story flags proving you actually found the route
The path to it is less a branch and more a checklist written by a paranoid cyber god.
Visually it looks something like this:
graph TD
A[Ghost Contact] --> B[Ghost Shard]
A --> C[Ghost Route]
D[Blacksite Raid] --> E[Quantum Key]
F[Janus Lattice] --> G[Janus Token]
G --> H[Janus Root Phrase]
C --> I[Erebus Route]
E --> I
H --> I
B --> J[True Ending Check]
E --> J
G --> J
H --> J
I --> J
K[Stealth >= 7] --> J
L[Intelligence >= 7] --> J
M[Trace <= 6] --> J
J --> N[Prometheus Unbound]
That structure made the game feel less like “find the correct button” and more like “assemble a run.” Even though honestly speaking would if it was like a random spin any minute now.
But this is way more satisfying.
Endings: The Good, the Bad, and the …
There are 40 endings in total, and they are categorized into things like:
goodbadneutralsecrettrue
That was important because I didn’t want endings to be binary morality stickers.
Sometimes an ending is “good” because you escaped cleanly. Sometimes it’s “neutral” because you won but now the entire sector is politically on fire. Sometimes it’s “bad” because you got turned into a lesson by a warship AI. And sometimes it’s “secret” because I think games should occasionally reward nosiness.
I love endings that feel like little genre pivots.
One route can make you a ghost myth in military channels. Another turns a prison ship into a revolt. Another has you basically sell the abyss piecemeal to the highest bidder like some sort of ethically compromised space stockbroker.
I wanted to add a part where you would end up on a strip club pole but seems like for now that’s a no go since I suck at graphics … but maybe … the chances of you getting killed by a chicken are low but never zero.
My favorite part is that each ending is not just a final line. It has:
- a node text payload
- an ending title
- a category
- a summary
So I could separate the dramatic in world payoff from the “what did you actually unlock?” metadata.
Random Events: Because Static Branches are good boys
idk why but this naming this title makes me wonder if I’m a telegram sex bot…
Pure branching logic is cool, but it can get a little too clean. Too deterministic. Too “I know exactly what the machine is doing.”
I wanted some runs to feel like the system was watching back a little.
So I added random events.
They are small but they do a lot for the mood:
- high trace can trigger detection events
- ghost contact can randomly grant a
ghost shard - low trace can reward you with a burner credential
- rival hacker flags can create hostile interference
- once in a while a hidden developer-ish message blinks through the cracks
That logic is essentially probability layered on top of state:
graph TD
A[Choice Resolved] --> B[Roll Random Event]
B --> C{Trace High?}
B --> D{Ghost Contact?}
B --> E{Low Trace?}
B --> F{Rival Active?}
B --> G{Tiny Weird Secret?}
C --> H[Increase Danger]
D --> I[Grant Ghost Shard]
E --> J[Grant Burner Credential]
F --> K[Hit Rep and Trace]
G --> L[Hidden Comment]
It is not roguelike level chaos or anything. I’m not trying to make Hades in a terminal here. (gotta give me that one, its a banger lmfaooo)
It’s just enough unpredictability to keep runs from feeling mechanically flat. cause I’m not building the next quantum computer here you know what I mean….
The UI: Dramatic Little Green Computer Nonsense
This game absolutely benefits from being in a terminal.
If I had built it as a plain webpage it would’ve worked, sure, but I would’ve lost a lot of the texture. (i still do plan on hooking a gui to this some day)
Textual let me do:
- a story panel
- a sidebar with live stats and inventory
- a highlighted choice list
- modal alerts
- save/load slot screens
- subtle glitch effects when danger spikes
So instead of feeling like a menu, it feels more like you’re operating some illicit military interface you definitely should not have access to. (I should have been born in the 90s man…. or am I?)
That matters more than people think.
Interactive fiction lives or dies on presentation. If the text is the main thing, the container around the text has to help the illusion, not fight it.
And frankly, green text with a little paranoia in it goes a long way. (if you do take LSD while playing it do let me know, I look forward to your feedback)
Save Slots and Replayability
If you’re making a branching narrative and you don’t let people save, that is between you and whatever demons are advising your UX decisions.
Dev notes this is a side quest please feel free to skip :
Okay so I basically thought I had this figured out on the first run and luckily my sycophantic partner in crime coding agent kept telling me it was the right decision and I also kept telling it that it was a genius and we had this huge circle jerk only where I’m burning tokens and getting closer to my rate limit, So then I thought everything was set and done and did my final push, and luckily the kind soul (lab rat) who had enough autistic brain as me and attention span that was not botches tried it and then went ahead to face some complication with saving and loading, I mean what kind of game developer says they have finished making a game without making sure the save mechanism are fully functioning that is definitely an L on my side but now its fixed and everything is changed, So here is the updated method I used and honestly if you wondering why I am writing this I don’t know too that’s why I said feel free to skip but thank you for sticking it out too and Just a little push, keep going.
The game has multiple save slots, plus global progress tracking for endings and seen nodes. That means:
- you can keep different runs alive
- you can retry dangerous routes
- the game remembers what you’ve discovered across playthroughs
This was important because the structure is built for replay.
The game doesn’t just say “start over.” It says:
“Cool. You died, got blackmailed, started a mutiny, and woke a forbidden archive. Want to see what happens if you’re slightly less reckless next time?”
And the answer, obviously, is yes.
The Funniest Part
The funniest part of building this thing is that I kept telling myself:
“This will be a small project.”
Which is a lie every programmer tells themselves right before inventing conditional story graphs, reputation systems, item gates, secret endings, random encounters, modal save screens, and a terminal interface that needs to look cool when a dreadnought tries to kill you.
So yes. Small project. Totally.
At the end of the tunnel …
There is still a bunch I want to do:
- more hidden routes
- more strange crossover states between major ship lines
- cleaner ending discovery views
- more reactive text based on prior runs
- maybe more “you found the developer room because you were snooping too hard” energy
- a bit of graphics sprinkled here and there
I also think there is room to make the game even more self-aware about choice, consequence, and replay without getting too far up its own ass about it.
Because that balance matters.
I want it to be smart, but I also want it to be fun. I want it to have system depth, but I still want someone to laugh when they get a deeply cursed ending title.
That part is important to me.
Till we meet again …
Prometheus started as me wanting to make a branching sci-fi hacker story with a terminal aesthetic.
Then it became a tiny game engine. Then it became a content graph. Then it became forty endings and a pile of conditions and secrets and routes and “wait what if this item unlocked that path over there” moments.
And honestly? I had a great time making it.
Bandersnatch was definitely one of the sparks. Not because I wanted to imitate it scene for scene, but because it reminded me how much fun it is when interactive stories acknowledge the player as part of the machine.
I wanted Prometheus to have a bit of that energy.
And if you play it and at any point think:
“Oh this is sick, I probably should not have done that”
then perfect. That’s exactly the feeling I was going for.
Thank you for sitting through all that, get a job mate… but still thanks for being here ❤️
Project source: github.com/Dawit-Sh/Prometheus
Stack: Python - Textual - Rich - JSON