Highway 404: a JS13KGAMES 2020 post-mortem

My fifth year participating to JS13KGAMES (the month-long game development competition in under 13 kB of HTML/CSS/JavaScript that runs from August 13th to September 13th) was a difficult one in the making.

I created Highway 404, an arcade driving game featuring local motorway ON-404, for the theme “404”. It placed 9th on desktop, 6th in mobile, and 12th in Web Monetization. It also scored 1st place for best theme integration and 6th place for best input controls.

Top-down view of a lone car on the tarmac, passing a sign stating ‘Highway 404’ and listing Toronto as the next destination.
Highway 404 reminded players of classic arcade games Spy Hunter (Bally Midway, 1983) and Bump’n’Jump (Data East, 1982)

Try it on JS13KGAMES or on my personal website, and have a look at past postmortems of my previous entries Blade Gunner (2016), A Tourist In Paris (2017), SUBmersible WARship 2063 (2018) and Don’t Look Back! VR (2019).

Foreword

Making my JS13KGAMES entry this year has been a real personal challenge. First, I was let go at the beginning of summer and busy job hunting during a world-wide pandemic while looking after my kids (since all summer camps were cancelled). Next, the family went on a (much needed) camping trip on the first week of the contest. Finally, it took me a long time to get inspired by the theme “404”, leaving me with only 13 days (how fitting!) to build a game and submit.

Each year, I focus on trying something new. My first entry “Blade Gunner” was about proving I could ship a simple, one-screen game. After that, I dabbled with procedural level generation, viewport camera, AI, vector graphics, and WebXR.

This year, I stuck to what I knew and doubled down on theme and “game juice”. I will cover here the many decisions made during development in no particular order. Feel free to jump to the topics that most interest you.

1. Game ideation

I wanted to create a non-violent game that I would feel comfortable letting my kids play. I didn’t find much guidance on gameplay in the theme, as 404 Not Found is usually an error one doesn’t recover from. Not much guidance for gameplay.

I had a glimpse of a puzzle game where the player would navigate a robot/vehicle/spacecraft through levels while different sub-systems of the machine went missing, thus forcing the player to change strategy regularly (think Regular Human Basketball by Powerhoof), but lacked inspiration to design interesting sub-systems and challenging levels.

My home province of Ontario (Canada) labelled its highways in the 4xx range. How cool would it be to feature the real highway ON-404 which I drive from time to time?

Highways 401, 404 and 407.

Andrzej Mazur, the JS13KGAMES master of ceremony, said on Slack “Highway Not Found sounds like a cool offroad racing game”.

The conversation that started it all

I was reluctant to relegate the highway to a MacGuffin. It had to be front and centre, not an inconsequential goal to reach. I had almost giving up when I realized that 404 Not Found could mean portions of the highway were missing. A vision of a car racing against strips of falling tarmac quickly followed.

The feeling I tried to capture in Highway 404

Exploring other HTTP status codes from this angle became promising: 501 Not Implemented could mean a lane wasn’t constructed yet, 503 Service Unavailable a lane under repair, etc.

I was still missing that tension I try to include in my gameplays (something forcing the player to alternate between non-optimum strategies rather than always follow the winning one) until a friend suggested collecting teapots after HTTP status code 418 I Am A Teapot. And thus came the tension: Would the player risk a collision in dangerous lanes to collect these teapots, or would they play it safe and leave behind precious points?

2. Core mechanics

I decided the player would try survive the perils of driving on Highway 404 for 6 minutes and 44 seconds, aka 404 seconds (see what I did there?).

The following HTTP status code would serve as the building blocks for highway challenges. I took great care to respect the status range semantics when designing their effect (aka 100 is informational, 200 is good, 300 is transitional, 400 & 500 are bad):

  • 103 Early Hints → dispenses in-game instructions. I initially planned to use 100 Continue as a way to encourage the player throughout the game, but that HTTP status code itself doesn’t do much. I found 103 Early Hints more appropriately named to warn the player of dangers ahead.
  • 200 Road OK → cancels the adverse effects of 404 Road Not Found. Having a falling road is nice and all, but left rampant it would quickly cause problems with the following challenges. What better way to counter it than using the universal HTTP status code for “All is well in the world (wide web) because I found the page you requested”?
  • 301 Road Moved & 302 Temporary Road Redirect → moves the player to a different lane (to the left for the former, and the right for the later, by a number specified in the level configuration). Okay, okay, I know that 302’s real status is “Found” while “Temporary Redirect” belongs to 307, that 301 is permanent, and yada yada, but after much reflexion I concluded that people might be more familiar with the historical 301/302 codes than their newer counterpart 307/308 and the subtle difference between the 2 pairs, so I took some creative liberties with the HTTP standard!
  • 404 Road Not Found → breaks the road behind the player. The star of all HTTP status codes and theme of this year’s JS13KGAMES competition had to be the most memorable and dramatic, so that’s the first one I implemented. Racing against a falling road that’s inexorably catching up on the player ought to fill them with dread!
  • 418 I Am A Teapot → collects these for extra points. They are the side quest of Highway 404, the ones that will trick players into taking unreasonable risks to increase their score.
  • 501 Road Not Implemented & 503 Road Unavailable → blocks the road ahead of you. These status codes were natural fits to reduce the number of drivable lanes and vary the highway landscape.

3. Web Monetization

Trivial to setup: check if it’s already started, and if not listen for the event fired when it does. The hard part is coming up with extra content that enhance your gameplay. In Highway 404, Coil subscribers get a different car skin and an extra HTTP status code:

  • 429 Speed Limiting → slows down time for 5 seconds. I placed these signs near some of the hardest teapots to collect to help Coil subscribers improve their high score. I used linear-interpolation to progressively slow down the player’s car when triggering speed limiting , and then speed it up once the effect wears out.

Speed limiting actually caused an epic bug: Due to some oversight, only the player’s car was initially slowed down by 429 signs, leaving it at the mercy of falling roads arriving at full speed behind it. Quite a poisonous gift to reward paid subscribers! Fortunately I was able to catch that bug in the nick of time before submitting my entry.

4. Puzzle design

My dream was to generate the highway challenges procedurally. Changing the seed would guarantee infinite replay, while sharing a one with a Tweet would entice followers to load that particular level and beat the high score. But I resolved to craft these challenges by hand so I could first figure out the game difficulty balance.

The highway is organized in short sections (10 to 15 seconds each) starting with a row of 103 Early Hints, as a fair warning to the player of the dangers ahead, and to build up anticipation and excitement. The first couple of sections act as a short tutorial, introducing new HTTP status codes one at the time. Of course, the first to appear is 404 (road not found), to drive home the competition’s theme as a game mechanic.

Designing some challenges with pen and paper

While designing sinuous obstacle courses, I quickly had to make a major decision: would the player be punished if they took a wrong turn, or would there always be a way out? SPOILER ALERT! After some soul searching, I opted to make the game about scoring the most points, and not memorizing the only path of survival. Sure, you can still loose if you veer into a missing lane, but when it seems the road is gonna stop abruptly rest assured there will always be a 301/302 to get you back to safety. SPOILER END! This decision has been really well received by the judges, as it keeps the game accessible to the more casual players and let them enjoy the driving.

5. Ending

Crafting these highway challenges by hand took a lot of time. On the last day of the competition, I had only managed to produce 1 minute and 15 seconds of content. To bring closure, I used rows of 103 Early Hints to break the 4th wall by thanking the player for completing all the challenges and wishing them a safe drive for the next 5½ minutes .

Then on a whim, SPOILER ALERT! I decided to taunt the player from time to time for continuing to drive on an empty road, and had fun writing quips: I slipped references to the Canadian “eh”, and quotes from local comics celebrity Scott Pilgrim (“shouldn’t you be working or whatever?”) and GlaDOS/Portal (“what if there is no cake?”).

Some of the quips along the way to the game end.

This kept the player engaged while sowing doubts about their persistence till they reached the very end where I rewarded them with a row of teapots for such tenacity. SPOILER END!

6. High score

In Highway 404, the player earns point for each second driven without accident, and for each teapot collected. I wanted the later to be an order of magnitude more so teapots would really be worth the risk. I opted for 10 points per second, and it felt fitting to value each teapot 418 points just like the HTTP status code they represent ;p)

Based on this, I estimated that the maximum score for regular players is 8,220 (404 sec x 10 pt + 10 teapots x 418 pt) while Coil subscribers could theoretically achieve 9,056 by using the speed limit signs to collect 2 extra teapots.

To achieve this high score, SPOILER ALERT! veer on the rightmost lane as soon as you start to collect a teapot hidden below the Highway 404 sign, then grab all next 7 teapots, and collect 2 teapots at the same time when you reach the finish line by driving in between 2 lanes. As a Coil subscriber, when you trigger the first speed limit sign you have a short window to zip across the tarmac and collect 2 teapots on the same row. SPOILER END!

7. Game juice

Once the basic gameplay was covered (car moving left and right, highway scrolling at full speed, car falling when touching a missing lane), I focused on “game juice”.

First, improve the game over experience: I spun and shrank the car to give the impression of falling into a pit. This led to a fun bug when I forgot to end the game once the car scale had reached zero: it continued to decrease in the negative which the Canvas API still treated as positive, spinning an ever-growing car.

A fun and unexpected bug

This bug turned into an inspiration for 301/302 signs: initially the car just swayed to another lane. It was bland and problematic when crossing a missing lane. To explain why the car would survive such situation, I scaled it up then down: this gave the impression it jumped over the gap and led to more climatic challenges.

My eldest son also took an interest in the game’s development. He suggested the car should slow down when driving on falling portions of road to urge the player to get out of the lane. Drag would only accelerate the player’s demise (the very thing we were trying to prevent) but the feedback was justified: Instead, I progressively shrunk the car to show it would disappear if the player didn’t act quickly.

Next, I rotated the car sprite slightly in the direction it was turning. The change in angle greatly improved the enjoyment you got from swerving between lanes.

Finally, I used linear-interpolation (or “lerp” for short) to accelerate the player’s car from 0 to cruise speed at the beginning of the game, and slowing it down to a full stop when loosing. Lerp was also useful for the temporary speed reduction when driving over 429 signs, and making the touch & keyboard controls super smooth.

8. Konami code

You may have noticed that Highway 404 doesn’t start immediately if you happen to press the ⬆ key versus any other key on the title screen. That’s because it’s waiting to see if you’ll complete the Konami code sequence (⬆⬆⬇⬇⬅➡⬅︎➡︎B A) without any mistake.

Since I would only get a 6-months free Coil subscription until after submitting my entry, I decided to use the Konami code as a backdoor unlocking the extra content reserved for Web Monetization which greatly facilitated testing.

9. Mobile first gameplay

The mobile category is always less crowded than the desktop/overall one (81 entries versus 212). If you’re looking for an easier way to place into a Top 10, make sure to add mobile support to your entry. That remains highly contingent an input scheme being portable to touchscreens.

I’m personally not a fan of adding buttons on the screen to replace the keyboard/gamepad due to a poor usability experience: The player’s fingers are likely to slip beyond the position of these buttons, leading to an awkward and sudden loss of controls breaking the game immersion.

Therefore, I made a point to come up with a gameplay requiring the least amount of controls: in Highway 404, moving the car between the lanes is that’s needed and can nicely be achieved with touch swipes. This gave me the opportunity to use a D-pad emulation I had created a while back but never got a chance to use yet.

Since this video was taken, I‘ve made swipes even smoother with linear-interpolation (or lerp)

Swipes can be initiated from anywhere on the screen so you never lose control. This effort paid off with the 6th place in the Mobile category.

10. Art & graphics

Pressed for time, I went back to my pixelart beginnings (see my 2016 entry Blade Gunner) and opted once more for a top-down view using the Pico-8 palette, whose vibrant colours are well suited for arcade games.

I jumped into Aseprite (my new pixelart editor of choice) and got started on the base sprites: the verge, shoulders, and tarmac of the highway, the car, and the falling road animation (which is rendered on top the tarmac sprites, progressively obstructing them)

Sprites from Highway 404, depicting the cars, sections of highway lanes, and various road signs
The complete Highway 404 spritesheet. The animated arrow signs on the last row aren’t used in the game.

The Highway sign only came after I settled on a sprite size (20x20). The road signs came even later to complement the HTTP status codes, and make them more legible. Those actually took longer to get just right, and fell under 2 categories: markings with a flat design (road cracks, change lane arrows, etc) and obstacles with a top-down perspective (construction barriers & cones, teapots, etc).

Fun fact: Canada and the United States are the only 2 countries in the world that don’t use the almost universal “red circle” for their speed limit signs, and instead insist that you read some text while zooming past at a presumably fast speed.

11. Parallax & Infinite scrolling

In Highway 404, the verge, shoulders and 7 lanes are rendered once and cached in a separate canvas. An array acting as a Display List keeps tracks all the other game entities to render (obstacles, car, …).

On every frame, the highway canvas context is copied onto the viewport canvas context, wiping the previous frame clean. Next, the sprite of each entity in the Display List is painted over. The player’s car is always drawn last, so it appears above everything else (with the exception of the Highway 404 sign which is briefly last in the Display List to give the impression that the car drives under it).

The motion is achieved by progressively moving the highway canvas down the viewport. Note that this canvas doesn’t contain 6 minute and 44 seconds worth of highway sprites. It’s just long enough to repeat seamlessly when the viewport reaches its end by moving it all the way up (so the viewport sits at its beginning again).

I ended up reducing the highway scrolling speed by half by the time I submitted the entry.

All game entities (car & HTTP status code signs) are created in the Display List at the beginning of the game. The main reason for this is so falling roads (triggered by 404 Road Not Found signs) spreading past the top of the viewport can be stopped by reaching a 200 Road Ok sign (otherwise they would go on endlessly, and the player could never cross that lane again). As an optimization, entities moving past the bottom of the viewport are removed from the Display List, as they’ll never be rendered again or involved in a collision check.

12. Level format

I’ve always been curious how shoot’em ups like Gradius or Gun Smoke encode their levels, but never found any article on the topic. I debated between indexing each entity (HTTP status code signs) by the distance or the time at which they’re encountered on the highway.

At first, I used distance indexing. It was easier to visualize their relative positions with the number of 20x20px tiles that separated them. However, the obstacles were too hard to avoid at the current vertical scrolling speed of 10 tiles per second. I was wary of spacing them out further, as it meant adjusting several distances by hand (yes, this year again I fail to create a visual editor to configure the level), so I reduced the speed to 8 tiles per second instead.

If the obstacles retained their arrangements, it now took longer to arrive at each section. I was still forced to adjust all distances, and it was harder to estimate by how much they needed to be reduced to maintain a fast pace in the game.

Therefore I switched to time indexing. It guaranteed that each section or sign would be encountered at a precise second, but it made it trickier to layout obstacles in relation with each other. In addition, decreasing the vertical scrolling speed now spread the obstacles apart further (so their timing would remain constant), and vice versa.

I pressed forward despite these drawbacks to finish the game in time, but I would like to figure out an indexing method that is impervious to scrolling speed changes, make it easy to place level sections at given time yet maintain the distances between the obstacles they contain.

13. Sound & music

This year, Frank Force and Keith Clarke each gave the JS13KGAMES community a timely gift during the competition: ZzFX and ZzFX Music, respectively. The former is a super-tiny sound generator, and the later a music tracker.

I integrated ZzFX Music early on into my boilerplate while brainstorming ideas. Once I started on Highway 404, I was curious to know how much bytes a song would actually take up. I used the demo track (Depp Loop, author unknown) as a placeholder to reserve that portion of the 13kb budget. Since it fitted quite well the pace of the game, I kept postponing the composition of an original track in favour of more features and level design.

I pushed music so much out of my mind that I submitted the game without a “death” sound effect when the car crashes!

14. localStorage

Storing the player’s high score (which I misspelled “highscore on the end screen) in localStorage was a pretty easy win. It also allowed the high score to appear as a trophy in Frank Force’s OS13K entry.

Cross communication between games via localStorage

15. Speech synthesis

Since most people don’t know the meaning of each HTTP status code by heart, why not use the Speech Synthesis API to explain them out loud? I looked for a Speech Synthesis voice matching the player’s navigator.language , since an English voice may not be available on some computers around the world. This led to interesting gaming experiences, given this local voice still read English sentences.

At least Speech Synthesis was memorable ;p

I should have localized the spoken messages but didn’t have the time (nor the space) to handle this Herculean task. I also realize that had the voice read the HTTP status codes instead of their messages, no localization would be necessary. Then again, we rarely say “four hundred and four”; I would need to spell it “4o4” to achieve the common “four-oh-four” in English.

16. Twitter & URL Shortener

To increase discoverability, I offered the player the option to Tweet their score, so their followers learn about my game and try to beat it.

Make it easy for people to discover and share your game

Key information you always want to include in the Tweet’s template:

  • the score and name of your game (obviously)
  • your Twitter handle (so you get notified when someone posts their score)
  • the #js13k hashtag (so the JS13KGAMES Twitter account can retweet it to its followers)
  • the link to your game on js13kgames.com

I highly suggest the use of a URL Shortener like Bitly for the later to get some insights on click rates. Bitly also lets you customize the short link into something more memorable (I misspelled the abbreviation of Highway as “Hgw” instead of “Hwy”!).

There is a catch-22 when creating the short link because your game’s page won’t be up until your entry is accepted. This led to some interesting confusion when I tested the Tweet…

I was particularly on-theme this year ;p

Fortunately, the JS13KGAMES game URL can be predicted ahead of time since it follows the stable (so far) formula:

https://js13kgames.com/entries/<game-name-in-slug-format>

Note that all entries are located under that same path regardless which year they were submitted in. Be wary of potential name collisions; always check if your preferred game title hasn’t already been taken. I learned this the hard way in 2019 when I had to change my game title from “Don’t Look Back” to “Do Not Look Back” at the last minute since the former had been claimed in 2017.

17. Dev journal & CCapture.js

It’s always inspiring (and sometime a bit stressing) to see other JS13KGAMES participants’ progress posted on Twitter and Slack. Notes taken during development also come in very handy when writing a post-mortem after the competition.

That’s why I decided to maintain a dev journal by posting one daily progress update in a Tweet chain. I made a point to always have some visual improvement to share, be it new gameplay, graphics or highway challenge, along with the current game size as an additional data point. Sharing a dev journal on Twitter also allowed me to get into my competitors’ head ;p

Twitter as psychological warfare

Since most updates were better conveyed with motion, I used CCapture.js (suggested by another JS13KGAMES participant) to record videos of Highway 404, and transcoded these WebM files to MP4 with an online video converter so Twitter could ingest them.

CCapture.js is quite straightforward to setup. Drop this library along your other JS scripts:

<script src="CCapture.all.min.js"></script>

Create an instance, start it (for me, as soon as the title screen loaded), take a snapshot of the canvas after each render, and save(when pausing the game or losing, depending on the daily update)

var capturer = new CCapture( { format: 'webm' } );// start the recording
capturer.start();
...
// capture the latest canvas frame at the end of your render loop
capturer.capture(canvas);
...
// optional if you won't capture multiple videos
capturer.stop();
// download automatically a file called {name}.webm
capturer.save();

I kept this code patch as a git stash and applied it at the end of each coding day. I might actually make CCapture.js a permanent feature of my gamejam boilerplate code, and tweak the build script to strip it out when prepping the game submission archive.

18. Highway 404 in numbers

All the game code in 1 image, broken down by purpose

Raw source:

  • 70,646 bytes of Javascript in multiple files (including comments & utility functions that may not be used)
  • 3325 bytes of images
  • 400 bytes of HTML

Submitted entry (Rollup > Terser > html-inline > html-minifier > zip > advzip):

  • 10,992 bytes of optimized Zip containing a single index.html file with all resources inlined.

Conclusion

Creating Highway 404 has been quite a journey, but these efforts were worthwhile and fulfilling. I’m very pleased with its rankings (my best year so far). The enthusiastic reactions of friends & JS13KGAMES participants after trying the game out or learning about its making were the best feeling and reward.

Don’t miss next year’s edition as JS13KGAMES will celebrate its 10 year anniversary!

Appendix: 13 other JS13KGAMES 2020 games

I thought I would end this writeup by featuring 13 entries that didn’t place in the Top 10 but that I really enjoyed playing nonetheless:

Heaven Ascent is an amazing pixelart platformer with an ominous soundtrack and rich in depth as the player progresses.

Electron Life Simulator is an experience out of this world, genially evil and supported by a whimsical music.

Rescue Not Found is a clever game of dice rolls featuring an engaging goal, cute pixelart, a soothing tune and excellent mobile support.

Symmetry not found is a smart and fast paced puzzle game with stunning visuals and electronic music.

La Once tells a heartwarming story with a unique visual style. So cute!

4o4 is a game like no other and could be used as an interview test for IT professionals.

Sojuz 404 is a great point’n’click adventure game with a nice flat graphics style and compelling story.

404kph is a stunning racing game with a retro vaporware vibe.

The Waffle is an amazing escape-the-room game reminiscing the chills of the movie Cube.

Byte! Deliver Yourself is clever puzzle game with nice visuals, similar to Not Found yet different.

Coler is a hardcore pico8 pixelart platformer mixing The Matrix’s background with Super Meat Boy’s “juice”.

And then it was gone is another super polished pixelart platformer requiring lightning reflexes.

Finding Paige is emotional and moving game about coma and life experiences with an electronic soundtrack of the caliber of Dune: Spice Opera by Exxos.

Software engineer turned manager and father of four, I create pixel art, video games and visual experiments on the Web.