Reading 1996 archives in a browser tab
A port has to start somewhere, and for this one it started with bytes. Before a single settler could walk anywhere, the game's archives had to be readable — in TypeScript, in a browser, without a native helper of any kind.
The formats
The original game keeps almost everything in LST containers: a header, a count, and then a run of entries where each entry says what kind of thing it is and how long it is. Inside sit bitmaps in four different codecs — run-length encoded, player-colour, shadow, and raw — none of which store colours. They store palette indices, and the palette arrives separately, out of an ACT or BBM file.
That indirection is not an inconvenience; it is a feature the game leans on hard. Player colour is a block of palette indices that get remapped per player, which is why the same soldier sprite can be red for you and blue for your neighbour without a second copy of the art. Animated water and lava are the same trick applied to time: the terrain palette rotates a range of indices on a timer, and the sea moves without anything being redrawn.
BOB files are sprite sets for people, organised so that a worker's body, his walking direction, his animation frame and whatever he happens to be carrying are separate lookups that get composed. SWD/WLD map files are a fixed 2,352-byte header followed by fourteen fixed-size layers, one byte per node: altitude, two terrain triangles, objects, animals, building quality, resources, shadows, lakes.
Composing, then packing
WebGL does not want thousands of small textures, and it does not want a paletted image at all. So the pipeline does two things before the renderer ever sees a sprite.
First it composes. A single drawable frame is usually more than one archive entry: a body, its shadow, and a BOB overlay for the ware in a carrier's hands. Those are merged into one RGBA "smart bitmap" with the player-colour indices resolved, so the draw call is one quad instead of three.
Then it packs. Frames are shelf-packed into a small number of atlas pages. The terrain texture is a special case: every palette-rotated water and lava frame is baked into one contiguous vertical strip with a fixed stride, which is what lets the water animate by writing a single shader uniform instead of rebuilding geometry every few frames.
Two details in that step are load-bearing and easy to get wrong. Two palette indices have to be keyed transparent — the usual magenta key, and index 0, because index 0 is the background of the edge-blend and road strips. Miss the second one and every terrain edge triangle paints a solid patch.
Doing it once
All of this is real work, and it does not belong on the main thread. The atlas build runs in a Worker and the result is cached in IndexedDB, keyed by a hash of the source files. The first visit pays for the decode; later visits start from the cache, and changing the asset pack changes the hash and invalidates it automatically.
The payoff for doing the decoding in the browser rather than pre-baking assets into the build is simple: the game does not ship the original artwork at all. The host points at a compatible asset pack, the browser reads it, and what lands in the repository is a parser rather than a copyright problem.