Fixing the Bugs a Code Review Found

Category: SDL Adventure Game

With the engine split into adventures and Gina the hen now living alongside the fox, the codebase had grown enough that I wanted a second pair of eyes on it. So I ran the engine through another round of the AI-assisted code review I’ve leaned on before — this time file by file, all the way through main.c, game.c, asset.c, actor.c, image.c, sound.c, and scene.c.

A backlog, first

The review came back long, so before changing anything I triaged it into the backlog. That step matters more than it sounds: a review mixes real crashes with style opinions, and some of its suggestions were already handled — the audio chunk size was fine, a couple of the “this will clobber” worries turned out to be safe because the calls happen to run in sequence. Sorting the genuine bugs from the nice-to-haves left a short, honest list of things actually worth fixing. Then I picked the top of it.

One callback for everyone

The clearest bug was a single line:

1
static void (*on_end_walking)(void);

That’s the “what to do when the character finishes walking” callback — and it was a global. With one actor on screen it works. With two, the second actor_walk_to overwrites the first’s callback, and when the fox arrives it runs the hen’s errand instead of its own. It’s the classic C single-global-callback trap, and it had quietly survived the whole move to a generic actor. The fix is to put the callback on the Actor itself, and to clear it before firing it, so a callback that kicks off a new walk doesn’t get immediately overwritten.

One halt too many

The next one is the kind of bug that only shows up with a toddler holding the device. When an actor stops walking, the engine halts its footstep sound:

1
Mix_HaltChannel(actor->move_sound_channel);

But that channel defaults to -1, and it stays -1 if the sound never started — for instance when every mixer channel is already busy because someone is spam-tapping the screen. And Mix_HaltChannel(-1) doesn’t mean “halt nothing”, it means halt every channel: the music, the dialogue, all of it. So in exactly the moment a two-year-old is mashing the screen, the voice line cuts out. Guarding the call with channel >= 0 keeps a stray stop from taking down the whole mixer.

The smaller sharp edges

A few more, each small on its own:

The fix I had to back out

The headline item from the review was a real one: mouse coordinates come in as window pixels, but every hotspot in the game is defined in the logical 800×600 space the renderer scales from. On a resized or high-DPI window the two don’t match, so clicks land in the wrong place. The textbook fix is to convert each event once, centrally, with SDL_RenderWindowToLogical.

I wrote it, and it broke the web build.

It turns out that on the browser, Emscripten’s SDL already hands you mouse coordinates in logical space — which is exactly why the game has always worked online without any conversion. Converting again scaled them a second time, and on a high-DPI canvas that put every tap in the wrong spot. So the “obvious” fix is really two different fixes: convert on the native desktop, where events are in window pixels, and leave the browser alone. That needs testing on a real resized window and an actual iPad — neither of which my headless smoke test can stand in for — so I backed it out and wrote down what I’d learned in the backlog rather than ship a fix I couldn’t verify. Knowing why a change is wrong is worth a commit on its own.

Where it stands

Everything that did land is small, contained, and checked: the desktop, terminal, and web builds are green, and the full headless playthrough still walks Gina from the shade to the pool without missing a beat. The rest of the review — a caller-owned asset path, a hardened sprite-sheet parser, the coordinate fix done properly — is queued up and waiting. You can still play the current build at potomak.github.io/VaniaVolpe.

Next: Where Should an Animation Update Itself?