โ† projects

How the game is built

write-up a playable portfolio ยท C++17 โ†’ WebAssembly

The game on the front page isn't a template or a toy built on someone else's engine. Every system in it โ€” how it draws, how it fights, how the crowd moves, how the story advances, how it ends up in your browser โ€” is code I wrote and can explain end to end. This page is that explanation, in plain language.

Why write the whole thing

I could have reached for a ready-made engine and shipped faster. I didn't, because the point of this project was to understand the machine underneath, not to hide it. Writing the loop, the renderer, the state machines and the build myself means there's no layer I can't reason about when something goes wrong โ€” and it forced me to make the kind of decisions a big engine normally makes for you: what runs every frame, what memory gets touched, what happens when the browser is the target instead of a desktop.

The result is small on purpose. It's a real game you can play in a tab, and it's also a readable codebase โ€” the two goals pull in the same direction when nothing is hidden.

The heartbeat: one loop, sixty times a second

Everything hangs off a single game loop that runs about sixty times a second. Each pass does the same three things in order: read input, advance the simulation by one small step of time, then draw the result. The discipline that makes it feel smooth is keeping every one of those passes inside its time budget โ€” a frame at 60fps has roughly sixteen milliseconds to do all of its work, and if a frame runs long the player feels it as a hitch. So the loop is deliberately boring and predictable: no surprises, no work that sometimes takes ten times as long as usual. Most of the interesting engineering in the game is really about protecting that budget.

Combat: timing you can learn, not luck

The fights are built as a state machine โ€” a small set of clearly defined states the encounter moves between: the player choosing an action, the enemy winding up a strike, a brief window where a parry will land, and then the resolution. The window that decides a parry is measured in milliseconds and is the same every time, so the outcome is deterministic: identical timing always produces the identical result. That's the whole design goal โ€” the difficulty comes from reading the enemy's tell and reacting, never from a dice roll you can't see.

The crowd: intelligence that isn't scripted

The ambient life moving around the world isn't following hand-placed paths. It's a flocking simulation where every agent obeys just three simple local rules โ€” don't crowd your neighbours, roughly match their heading, and steer toward the middle of the group. Nobody is in charge, yet coordinated, natural-looking movement emerges from those rules alone. It's a small showcase of a big idea: complex behaviour can fall out of simple parts, and that's often cheaper and more convincing than scripting every actor by hand.

The engineering catch is that each agent has to look at its neighbours every frame, which can get expensive as the crowd grows. Keeping that inside the frame budget is exactly the kind of constraint the whole engine is organised around.

Drawing the world

The visuals are deliberately pixel art, drawn by a small renderer of my own built on top of a lightweight graphics library. Art is packed into sprite sheets and stamped onto a grid of tiles, kept crisp with point sampling so the pixels stay sharp instead of blurring. A camera follows the player and is clamped to the edges of each room, so it never drifts off into empty space. Each act of the story is its own hand-authored map โ€” its own layout, props, characters and trigger zones โ€” described as data the engine reads, rather than logic baked into the code. That separation is what lets me add a new area by editing data instead of rewriting the engine.

Telling the story

The narrative runs on its own small system of flags. Walking into a trigger zone hands control from exploration over to a scripted sequence โ€” a cutscene, some dialogue, a fight โ€” and when that finishes, control passes back to exploration with the story's progress recorded. It's the same well-worn architecture classic RPGs use, rebuilt from first principles: a handful of state variables gate what's available and carry the momentum of the plot forward, so the world remembers what you've done without any of it being hard-wired.

From C++ to your browser

The whole thing is written in modern C++ and compiles two different ways from the exact same source. One path builds a native program on my machine for fast testing; the other compiles it to WebAssembly โ€” a portable, near-native format browsers run directly โ€” so the game loads in any modern browser with no plugin, no install, nothing to download. Sharing one codebase between the two means I develop and debug quickly on the desktop and know the web build is running the same logic, not a separate port that can quietly drift out of sync.

Keeping myself honest

Because I'm the only one working on it, the game checks itself. A headless mode renders frames without a window so screenshots can be captured and compared automatically, and a small suite of self-tests asserts the rules that matter most โ€” a perfect parry must deal zero damage, timing windows must resolve the way they're specified. Those tests are how a solo project stays trustworthy: when I change combat, the machine tells me immediately if I broke a promise the game makes to the player.

The short version: a real, hand-written game that runs in a browser, built to be as readable as it is playable โ€” every system here is something I can open up and walk you through.
C++17raylibemscripten WebAssemblystate machinesflocking / boids tile rendererdeterministic sim
โ–ถ Go play it โ† back to projects