# Making a game in C for the Global Game Jam: Inside Job
At the beginning of this year I participated [once more]("ggj_25.md.html") in the global game jam. In this post I'll
talk about how my team and I designed a local multiplayer investigation game, how I implemented a
record-replay mechanic, the strange platform bugs we hit along the way, and how we still managed to make a game
we're proud of, using my engine written in C.

## Brainstorm
This year I teamed up with 3 other people: Sandro, a [game designer from the swiss scene]("https://www.sandrodallaglio.ch/"),
Camille, a [2d animator](https://camillebovey.com) (incidentally my girlfriend!), and David [a composer
and sound effect designer]("https://www.instagram.com/david_dg_music/") (incidentally my cousin!).
I was handling the programming side, using my C game engine as usual.
At 17:00, the theme was revealed: **Mask**. We started brainstorming right away.
In my experience, this phase of a game jam is rarely easy, but often quite exciting. We all suggested ideas,
bounced ideas off one another, spotted problems, proposed solutions... We also shared our own preferences in terms
of games, bringing up various references as we brainstormed.

It can be quite hard to know when to stop. Often a couple of ideas stand out, but it's tricky to get a sense
of whether a design will actually hold water once implemented. The best way to know is to prototype,
but the time constraint makes this impracticable. On the other hand, we want to start working on
an idea everybody is onboard with and that has a good chance of working.
Pretty early we had the thematic idea of the bal masqué. We figured we could take inspiration from cluedo, and
have a house with multiples rooms. "Mask" could have the double meaning of masking
information to some players. The game could be multiplayer, with one imposter that has a secret goal (e.g.
kill someone?). But depending on in which room the various players are standing at various points, they would
see different information and would have to piece it together (with the imposter potentially lying to throw off
their opponents).
We explored this idea a bit, and came up with a bunch of design ideas, but in the end we had one problem:
all of these ideas felt more like board games than digital games. Although we all like board games, we wanted to go for
and idea that made sense as a digital game and couldn't be done purely physically.
At some point in the brainstorm, I showed a prototype or a survival game I had been working on. In it, you
play a pixelized astronaut that explores an abandoned space station, riddled with monsters. A particularity
of that prototype is that there is a visibility system, where parts of the world that are not visible to the
character are darkened, like a shadow. This immediately resonated with Sandro, who could see our murder mystery idea
working in this context.

Here's the design we came up with:
The game plays with two players, locally on the same machine.
__Phase 1:__
One player plays as the inspector: they covers their eyes and "open their ears". The other person plays as the thief:
they takes the controller and move a corrupted guard around. They have a limited amount of time to steal as many
art exhibits as possible, preferably without getting spotted by other guards.
While the thief moves around, they make noise, potentially providing clues to the inspector. They walk on different kind
of floors, and have to break the glass of art exhibit displays.
When the time runs out, they have to blend in and position themselves as to pretend to be one of the guards.
__Phase 2:__
Now the inspector takes the controller. They must investigate, try to retrace the steps of the thief and arrest them.
On top of the auditory clues they got in phase 1, they can "interrogate" any guard which would then tell them
what they saw. We would realize that as a "replay" of what the thief did, form the point of view of the interrogated
guard. If the inspector interrogates the thief (who is also a guard), they would say they saw nothing.
With enough information gathered, the thief might be able to tell that one specific guard is lying and arrest them!
## "Proof of concept" prototype
At this point everybody was mostly convinced, and we figured we could quickly put
together a prototype using my space survival game as a basis, so we just got started.
Sandro made a level in the simple level editor that I had made for the survival game, I slapped a countdown
display in the top right, and we were already playtesting to validate our design. Instead of implementing the
snapshot feature right away (which would have been quite time consuming), we had a third person assist the
playtest and take literal screenshots every time the thief entered the line of sight of a guard. I thought
that was a nice way to quickly test out our design with the tools at hand!

## The technical difficulties begin
I had just finished moving the code from my space game to the jam project I had setup beforehand, and it was
time for Sandro to start iterating on the level and game design. Unfortunately, to my surprise, the game
wouldn't launch on Sandro's windows laptop, crashing at launch.
I tried to remain calm, and started investigating. From the stacktrace, it seemed that a texture couldn't be
created by sokol. Since I couldn't reproduce locally on my mac (yep, I'm sure you can see where this is going...),
I added some logging, made another build and had Sandro run it. The function `ID3D11Device::CreateTexture2D` was
returning the error code for `INVALID_ARG` when creating a texture destined to be rendered into, for the lighting effect.
I started to scrutinize the code around the texture creation, logged more values to try to understand the problem,
checked every argument — hoping I would see something obviously wrong like corrupted memory. But nothing jumped
to my eyes.
What was troubling was that it I worked perfectly on my mac, with the Metal api. Even more so, I remembered the
space game prototype running just as well on windows, with DirectX 11. So I had to somehow have introduced a
mistake when moving the code over to the jam project.
I was quite intrigued, and wanted to investigate more, but going back and forth between my computer and Sandro's
was starting to get frustrating for both of us. Then I remembered that I had parallels installed and could run
the game on windows, with the Visual Studio debugger attached. I crossed my fingers for the bug to reproduce in
that environment. And it did!
Sadly, even with that setup, I didn't manage to get more info. All the debugger gave me is the same INVALID_ARG
error code.
At that point, wanting to move on to prototyping the game, I gave up. But after the jam, with a clear head and less time pressure, I was able to pinpoint the issue. All I had to do was
to run the binary through Visual Studio with the D3D11 validation layer enabled, and look at the Output pane
(as opposed to stdout like I had been doing).
> `D3D11 ERROR: ID3D11Device::CreateTexture2D: D3D11_BIND_RENDER_TARGET and D3D11_BIND_DEPTH_STENCIL can't both be used together. [ STATE_CREATION ERROR #99: CREATETEXTURE2D_INVALIDBINDFLAGS]`
>
Whoops... I was indeed telling sokol to create a texture that could be bound as a render target and a depth
stencil. It wasn't an issue on Metal and webGL, but failed on D3D11.
```c
sg_image_desc img_desc = {
.usage = { .color_attachment = true },
.width = 256,
.height = 256,
.pixel_format = OFFSCREEN_PIXEL_FORMAT,
.sample_count = OFFSCREEN_SAMPLE_COUNT,
.label = "color-image"
};
sg_image color_img = sg_make_image(&img_desc);
img_desc.pixel_format = SG_PIXELFORMAT_DEPTH;
img_desc.usage.depth_stencil_attachment = true; // <---- my mistake here, not initializing the entire usage struct and leaving .color_attachment to true.
img_desc.label = "depth-image";
sg_image depth_img = sg_make_image(&img_desc);
```
Since then I contributed a [small PR](https://github.com/floooh/sokol/pull/1475) to sokol so it asserts if you
try to do this, hopefully that saves someone some time in the future.
## ...and continue
Since I couldn't solve that bug quickly in the heat of the jam, Sandro said he would go to his house and back to
get his mac laptop. This was a windows issue after all.
When he was back he booted the game without problems, as expected. Unfortunately, and to my great surprise, his
mouse position in the editor started lagging widely after a few seconds. The framerate of the game was fine, but
his apparent mouse position was delayed by an unpredictable and sometimes huge amount of time.
At this point I was quite embarrassed. I couldn't reproduce the issue on my own mac at all. I was running a
version of macos that was a few major version behind, whereas Sandro had updated to the latest, so I figured this
was the problem. Desperate to unblock Sandro, and not wanting to waste time fighting obscure platform bugs, we
took David's car and went to my office to get a mac mini with a macos version old enough to not exhibit the bug,
and setup Sandro to work on it. Problem sidestepped at last!
After the jam I figured it must have been this problem introduced in macOS 26 (Taohe): https://github.com/floooh/sokol/issues/1344
The issue mentions high polling-frequency mice, and Sandro had a mouse that looked like it could be high
frequency (I should check with him). I should also check whether the mitigations discussed in the github
issue actually fixed the our problem in practice.
## The game in the making
Sandro made a nice concept in the form of a flowchart with screen mockups. It was a good way to make the design
more concrete and answer some questions preemptively.

He also designed multiple levels, which we playtested at a few points during the jam. We kept the best 3 for
the jam submission.
David made some sounds effects for the guards, some footstep sounds on different floors, and two music tracks.
One nice touch with the music that plays when the inspector is investigating is that the track gets more intense
near the end, and stops right at the moment when the timer runs out.
Camille drew a new (high definition) tileset to replace the one from my space game. Then she worked on visual
designs for the two character and made their animations.

The finished cop and thief animations.
Finally, she made the screens Sandro had conceived into proper illustrations.
## Record & Replay
One of the core features of the game design is that when interrogating a guard, you see a "recording" of what
that guard has seen.
The replay mechanic in action.
When I sat down to implement this, my first thought was to leverage the replay feature built-in my engine. This
feature allows to rewind time and show the game as it was N frames ago, then replay what happened through recorded
inputs. After thinking about it a bit, it became clear that this wouldn't work out of the box:
- By default the replay system targets the whole game state. In the case of our game, we'd still need some
state to persist independent of the replays, such as the round timer.
- During the replay, we want to show the point of view of the guard instead of the player (the thief).
- The engine's replay system replays playing inputs. That works fine to replay the entire game, but in our
case it might be awkward: what if they open the pause menu for example?
Maybe if I changed the architecture and API of the engine's replay system, I could make it useful to implement
game mechanics. Providing the basic bricks to record pieces of state, along with specific inputs needed to
replay how they change over time should be doable.
What I ended up doing was much simpler. Every frame where a guard sees the player, they take a "snapshot" of the
whole entity array. The entity array happens to contains every piece of state that needs to be replayed: the player
position and facing direction, the art exhibits and their "stolen" state (intact or stolen), the state of other
guards (_in alert_ or not)...
Then, in the investigation phase, when the inspector interrogates a guard, I just replay these snapshotted
frames one by one. I achieved that by separating the simulation of the game logic and the drawing of the
scene. Conceptually, drawing the scene becomes a "pure function" that only takes the entities snapshot and which
entity should be used as a camera target as arguments.
```c
void game_tick(void)
{
// Simulate the game: player movement, interaction with guards etc
}
void draw_entities(int viewer_entity, entity_t* entities, int entities_count)
{
// Draw the scene using the passed-in entities as the only state, with
// viewer_entity as the target for the "camera"
}
```
Now all I have to do to replay a particular recording is to set `game_state->current_recording` to a recording
index, and the game will draw that recording frame instead the "current" game state.
These recordings could be stored on the entity that recorded them, but since I didn't need these recordings
to be themselves recorded by other entities, I choose to put them all in an array in the game state. Each
entity which has a recoding then stores an index into that array.
An advantage to having implemented the game's replay feature separately from the engine replay feature is
that I could then replay the replay. E.g. use the engine feature replay part of the game where the game
was itself showing recorded scene. It actually came in handy post-jam to debug a visual issue with the animation
in replays.
Down the line, I think I'd like to expose enough of the machinery of the engine replay system so it can be used
independently by games and tools, on the data of their choice. That way, more complex replays as game mechanics
could be easily archivable.
Note that by recording all frames and simply playing them back in order, I make the assumption that the fps
doesn't change drastically, which proved not to be a problem since I use smoothed frame times and always render
at the screen refresh rate. For a proper game release, a more robust solution could be to record at a fixed
rate and interpolate between snapshots. Clearly not needed for a game jam though!
## The result

In the end we had a functional game that we were proud of, and we were glad to see that people were having
fun playing it. I'm glad that we made a two players game, as I find the social aspect of one player
having to playfully deceive the other quite interesting.
Don't hesitate to try it with your friend / partner / kid / coworker / passerby !
## Takeaways
During this jam, several engine bugs slowed us considerably. This was a humbling experience: I realise
that these kind of problems would probably not have happened if we had used an off-the-shelf
engine like Unity or Godot. There were points in the jam where these bugs were completely blocking team members
from working, which felt was quite stressful.
Thankfully, in this jam, we were able to either solve or work around every problem, and the end result was
— imo — really good, so it eclipsed the doubts in my mind. But if we hadn't managed to make a good game, I would probably
have felt quite bad at the end.
All in all I think I was quite unlucky to hit these bugs all at once, and that on average the experience of
using my engine is much smoother (e.g. in 2025, it went much smoother in terms of engine bugs). Also, these
bugs are now fixed, and I now understand some things better and would be able to debug similar problems more
efficiently.
## Appendix: Stencil out
A gamedev friend, [Eike](https://bsky.app/profile/zet23t.bsky.social), was up to try my engine for that jam. He started earlier in the week, which
allowed me to help him set up and fix some platform-specific bugs he stumbled upon.
I'm grateful that he gave the engine a go despite knowing there would be problems. Even if his game was pretty
self-contained, by the end of the jam I had a list of things to improve.
I think the biggest thing that's missing right now — besides many fixes all around — are some good samples that
demonstrate how to use the various APIs and features of the engine. I've got to work on that soon.
The game he ended up making is arguably more of a toy:
> An experimental spray-can like painting app where you use stencils to create paintings.
>
> The project was the result of working on the Global Gamejam 2026. The topic was: Mask.
>
> I originally intended to make this a puzzle game but got hang up on the spray can mechanic as I found it too much fun to just paint stuff.

Try Eike's game here!