Bugs. Bugs Everywhere

3 minute read

44 Days Until I Can Walk

I started working at my new job today, which is always exciting. Thankfully I can work from home (or really, anywhere on Earth as long as I have an Internet connection). I’ve been reading through tons of project documentation to try and get up to speed with how everything in the project works, but that didn’t stop me from setting aside some time to tackle Fourteen Screws too.

I’ve encountered some strange bugs since refactoring the code. User movement no longer worked properly—bumping into walls that weren’t there— and the application would crash if I stood to close to a wall.

It’s quite interesting figuring out what a debugging workflow actually looks like when building WASM applications. There is no way (as far as I understand) to debug WASM in your browser. At least, if you set a breakpoint and trigger it in the debugger, you will find yourself stepping through raw WASM instead of your Rust code. For debugging tricky rendering bugs, nothing has been as useful as taking advantage of Rust’s unit testing framework.

I was able to nail down some consistent engine states which triggered a system crash by placing the camera in the corner of a test room and rotating it until the webpage stopped working. The value of the camera’s position and angle were logged to the console and these could be used to design a little unit test. I traced the issue back to my texturing code by selectively disabling bits of the rendering algorithm until I discovered that the application was trying to access texture data outside the bounds of the lookup table for defining texture step based on distance from a wall.

Eventually I realised that the lookup table was one row too small. The size of the lookup table was defined as follows:

const RANGE: i32  = (consts::WALL_HEIGHT_MAX - consts::WALL_HEIGHT_MIN);
const SIZE: usize = (RANGE * consts::PROJECTION_PLANE_HEIGHT) as usize;

let mut wall_texture_index: [usize; SIZE] = [ 0; SIZE ];

With RANGE being the zeroed range of heights that a wall can have. The problem is that the range [consts::WALL_HEIGHT_MIN, consts::WALL_HEIGHT_MAX] is inclusive, and so by this definition, the texure mapping information is not defined for the tallest possible walls. And when do we see these wall? Why, when the camera’s nose is pressed right up against them. After realising this, patching the bug was as simple as adding 1 to the value of RANGE:

const RANGE: i32  = (consts::WALL_HEIGHT_MAX - consts::WALL_HEIGHT_MIN);
const SIZE: usize = (RANGE * consts::PROJECTION_PLANE_HEIGHT) as usize;

let mut wall_texture_index: [usize; SIZE] = [ 0; SIZE ];

Tracking down the issue with movement was a little tricky. I was certain that the logic was correct. Bumping into invisible walls would imply that the movement algorithim was looking at the wrong wall data when trying to detect a collision. But the movement algorithm and the raycasting algorithm both use the same interface to determine the position of walls. So if the movement code was pulling on the wrong wall data, then the raycaster shouldn’t work either.

Figuring this one out was a little tricky, but I realised that what I had done was accidentally swap the x_walls and y_walls entries in the JSON file that defines the level. This should have triggered a bug in the raycaster, except that I accidentally swapped them again when refactoring the raycaster code. The two errors cancelled eachother out and so the map rendered perfectly, but movement didn’t work. This was an exceptionally stupid bug. But after squashing it, everything seems to work just fine.

I’m still not entirely sure if I’ve gained the speed boost I was hoping for during this reafactoring. I’m going to need to look at profiling my code soon so I can actually assess the impact of some of my design decisions, but I am learning an incredible amount from this engine project. As I said yesterday, I’m still not happy with how my raycaster works. I would really like to update it to use traits instead of function pointers, but that’s a challenge for another day.