---
title: "A 3 kB Windows game saved space by embedding its own virtual machine"
locale: "en"
url: "https://irz.fr/en/articles/shmup8-3kb-vm-game-en"
markdown_url: "https://irz.fr/en/articles/shmup8-3kb-vm-game-en.md"
category: "tech"
tags: ["demoscene", "game development", "language", "live coding"]
published_at: "2026-08-12T13:39:00.000Z"
author: "Camille Morel"
translation: "https://irz.fr/fr/articles/shmup8-jeu-3kb-vm-fr.md"
---

# A 3 kB Windows game saved space by embedding its own virtual machine

For Langjam, Laurent Le Brun built a language, a VM and a shoot ’em up in an executable of roughly 3 kB. The constraint ends up shaping the game design too.

Laurent Le Brun wanted to make a game in seven days. So he started by writing a programming language.

That is a terrible idea in most schedules and an excellent one for the Langjam Gamejam, whose rule was precisely to create a language and then use it to build a game.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

The result, shmup8, is a Windows shoot ’em up whose final executable is roughly **3 kB**.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb) The code is public and a playable build is distributed on itch.io.[2](https://github.com/laurentlb/shmup8) [3](https://laurentlb.itch.io/shmup8)

The interesting part is not only the number. The size constraint ended up shaping the language, engine, workflow and even what enemies are allowed to do.

## VM versus 90 bytes

Le Brun builds a compiler in F#, an interpreter in C++, then writes the game logic in his custom language. Rendering happens through a single fullscreen GLSL shader.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

You might expect the interpreter to be an absurd luxury when every byte matters.

At the end of the project, he ports the game logic to C++ to check. In his measurement, the version without the VM ends up **90 bytes larger** than the bytecode version.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

He explicitly asks readers to take that result with a grain of salt: neither the C++ port nor the interpreter was pushed through exhaustive optimisation.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

The sensible conclusion is not “VMs are smaller than C++.”

It is that a tiny intermediate language can offset the cost of its interpreter when it encodes one narrow game domain very efficiently.

## One impolite type

The bytecode essentially uses one type: `float32`.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

Values live in arrays. A local variable takes a slot. An index is also a float and gets converted to an integer when needed. A condition becomes true above 0.5.

The bytecode boils down to two broad kinds of operation: update a cell or jump to another address, optionally under a condition.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

The language visible to the author stays slightly more civilised through syntactic sugar: assignments, `if`, `while`, and `for` loops transformed by the compiler.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

The separation is conventional, but the constraint makes it unusually visible: the syntax can stay pleasant as long as the executed format remains brutally simple.

## Two live loops

The most useful part of the write-up may not be the compression.

Le Brun wanted to change logic and visuals without rebuilding the C++ project. On each source edit, his custom compiler produces new bytecode, and the already-running executable reloads that file every frame. The GLSL shader reloads in the same way.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

Two live-coding loops sit next to each other.

That preserves something technical constraints easily destroy: fast visual iteration.

Le Brun makes the point directly. In creative work, you cannot reliably predict what will feel good before trying it.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

## Immortal enemies

The game starts with three enemies and adds another every seven seconds.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

When a missile hits one, the enemy does not die. It gets teleported outside the screen and can return later.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

Why the unexpected immortality? A list of waves, destruction and more state would have required more logic. Le Brun chooses a simpler rule that still increases difficulty.

Runs are designed to last roughly 30 to 60 seconds, with fast restarts.[1](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)

The size limit therefore did not merely compress the finished program. It produced game-design decisions.

## Constraint first

shmup8 is obviously not a sensible architecture for the next 80-hour RPG.

But it demonstrates something transferable: a constraint becomes interesting when it influences the system from the beginning.

If you decide at the end that a game must be small, you compress assets. If you decide at the start that it must be tiny, you invent a bytecode, draw everything through one shader and ask whether enemies really need to die.

At 3 kB, even a game mechanic starts looking like a storage decision.

## References

1. [Laurent Le Brun, « Making a game on a custom bytecode VM in 7 days and 3kB »](https://laurent.le-brun.eu/blog/making-a-game-on-a-custom-bytecode-vm-in-7-days-and-3kb)
2. [Laurent Le Brun, dépôt GitHub shmup8](https://github.com/laurentlb/shmup8)
3. [shmup8 sur itch.io](https://laurentlb.itch.io/shmup8)
