# LineWars II, portable A playable reconstruction of the 1994 DOS game LineWars II, in portable C, targeting SDL3. The engine here is not a lookalike. Every function was reconstructed from the original game's machine code & then checked against it: 35 automated gates, 23 of which execute the original 1994 binary inside an emulator & diff its return values, its VGA port writes & its pixels against what our compiled C produces. Those gates run on any host, they came with the code, & they are the contract this port has to keep. ![The demo at the 1280x960 display mode: the 3D scene at 2x internal detail](docs/captures/11_highres_1280x960.png) ![The attract intro at 640x480, the mode that stays byte-exact against the DOS build](docs/captures/07_intro.png) ## About this port Native SDL3 port of the [LineWars II reconstruction](https://github.com/fredangstadt-lang/LineWarsII-DOS). That repo holds the full provenance, verification & how-it-was-made story. LineWars II is © 1994 Patrick Aalto; the reconstruction is released as freeware with his permission, and any commercial use requires a licensing discussion with him. The game core is the same verified C as the DOS build. Every hardware touch in the reconstruction funnels through a small, closed set of leaf functions, so this port is a new backend behind seams that already existed. That's why it went from nothing to playable in an afternoon. The DOS build's platform-independent gate suite runs against this build on the host. Adds over the DOS build: high-resolution rendering, resizable window, fullscreen. Builds on Linux and Windows (Windows binary cross-built under Linux, tested via Proton). Two porting bugs worth recording. The original depends on 32-bit integer wraparound, & on LP64 Linux the un-wrapped value surfaced as a lighting bug: the sun's position derives from the overflow. The other was filename case-sensitivity. Both fixed & play-tested. ## Where this came from The reconstruction was done in a separate repository, , & that repo stays exactly as it is. It holds the disassembly, the byte-offset annotations in every comment, the audit trail, and the DOS build. **When something in this port behaves wrong, that repo is where you go to find out what the original actually did.** This repo holds the clean version: the same verified logic with the archaeology stripped out, written to be read by someone who has never seen a 16-bit disassembler. ## Layout src/ the engine, portable C include/ its headers platform/sdl3/ the SDL3 backend platform/dos_reference/hal_dos.c the DOS backend, kept as the SPEC: every seam it answers, the SDL backend has to answer too tests/gates/ the verification gates (verify_*.py + their C runners) tests/oracle/ the emulator harness that executes the original's bytes tests/unit/ host unit tests tools/ asset extractor, asset packer, test runner docs/ port notes, architecture, coverage, asset format reference/original/ original game material (Patrick Aalto's work, see below) assets/game.dat the asset container (Patrick Aalto's work, see below) ## The port is a backend, not a rewrite Every hardware touch in the engine funnels through a small, closed set of leaf functions. The DOS backend answers them with INT 10h, INT 21h, VGA port writes and a Sound Blaster DMA loop. The SDL3 backend answers the same calls with a texture, an audio stream and a scancode queue. Nothing above the seam changes. The seams are inventoried in `docs/SDL3_PORT_NOTES.md`, seam by seam, with the SDL3 call that answers each one. ## Rules for code in this repo The DOS repo is an archaeology site and reads like one. This one does not. 1. **No byte-offset archaeology in comments.** `/* @0x1d8f the scanline loop */` belongs in the other repo. A comment here says what the code does and why, not where it came from. 2. **No magic numbers.** Every constant gets a name that says what it means. `LW2_TRIG_SHIFT` and not `14`. `LW2_VGA_BYTES_PER_ROW` and not `80`. 3. **KNF style** (the OpenBSD conventions), consistently. 4. **The gates stay green.** Any cleanup that changes a gate's output is not a cleanup, it is a bug. Run `tools/run_unit_tests.sh` before and after. ## Original game material The original game's files travel with this tree, redistributed as freeware with the written permission of Patrick Aalto, the game's author (`NOTICE.md` has the terms): - `reference/original/` - the original executable's code. The gates need it, because they run the real thing and diff our C against it. - `assets/game.dat` - the art, music and mission text, repacked into our own container. Both can be rebuilt from a copy of the game rather than taken on faith: `tools/extract_assets.py` unpacks the original, and `tools/pack_gamedat.py` builds `game.dat` from what it finds. Keep those tools working. They are how anyone else reproduces the container, and how you regenerate it if the format changes. ## Status The DOS build is complete & playable: all ten missions, byte-exact spawn tables, sound, cockpit, radar, explosions. It runs at roughly 47 fps in missions under DOSBox-X, against the original's 60. The SDL3 build is playable end to end. `linewars2.exe` runs splash, menu, intro, flight, debrief, the order screen & an in-game settings screen, with Sound Blaster Pro music & SFX through `SDL_AudioStream`, keyboard & mouse control. It builds for Windows (WinLibs gcc 16.1.0 UCRT, or mingw-w64 cross-compiled from Linux) & natively for Linux; the INDEX8 texture path & the audio stream work on the software/opengl/vulkan drivers. **Verified against the original, not against a screenshot:** - The renderer: `tests/gates/test_gfx_runner_sdl.c` links the shipping SDL backend into the oracle harness & diffs it against the original VGADRV machine code executing in Unicorn: 431 cases, 0 mismatches. `tools/run_unit_tests.sh` runs it on every invocation. - The menu: byte-exact against the DOS `SHOT01.RAW` cell dump. - Intro, flight & the demo: match the DOS reference captures (`docs/captures/`). - Audio: the mixer is shared source with the DOS build; the captured PCM's level matches the DOS reference within ~10%. Everything the player changes lives in a DOS-style tabbed settings screen (Graphics, Control, Sound, Exit), reached from the menu's Configure row, saved to `settings.dat` next to the .exe: the output resolution (640x480 up to 1920x1440, plus fullscreen), music & SFX volume, the 8x16 font, & every flight key rebound to any key or cleared. The resolution setting resizes the window for real & renders the 3D at 2x internal detail past 640x480; at 2x the fonts, cockpit sprites, gauge bars & radar blips all pixel-double to keep their size. 640x480 stays byte-exact & keeps every gate green. `docs/PORTING_CHECKLIST.md` is the plan & the status board; `docs/ROADMAP.md` lists the next features (all-side missiles, LAN play, a custom-mission launcher); `docs/captures/` holds a shot of each screen. `docs/SDL3_PORT_NOTES.md` is the underlying research, with an amendment box marking what it got wrong. ## Building SDL 3.4.12, built from source with the same compiler as the game (WinLibs gcc 16.1.0, `x86_64-w64-mingw32`, UCRT). The official SDL mingw dev release is MSVCRT-built & mixing CRTs is a bug hunt nobody needs. git clone --depth 1 --branch release-3.4.12 https://github.com/libsdl-org/SDL.git cmake -S SDL -B SDL/build -G Ninja -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/path/to/SDL3 -DSDL_SHARED=ON -DSDL_STATIC=OFF \ -DSDL_TESTS=OFF -DSDL_EXAMPLES=OFF cmake --build SDL/build --parallel && cmake --install SDL/build Then the game (point `CMAKE_PREFIX_PATH` at wherever SDL3 was installed above): cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=/path/to/SDL3 cmake --build build ## The decisions behind the port **We render our own 3D.** No GPU, no model conversion, no modern engine. SDL supplies a window, a texture, an audio stream & an event queue. The game draws a few hundred flat, untextured triangles a frame; software handles that in under a millisecond on any machine made this century, & the fixed-point math is the part worth keeping. **One chunky framebuffer.** The planar VGA address & the chunky pixel index are the same number (`pixel = 4*off + plane`, derived in `docs/ARCHITECTURE.md`), so the port keeps every verified rasterizer byte-for-byte & still gets a flat buffer, with no plane arrays & no per-frame conversion. **Pixel-exact at 320x480 first.** The engine renders into a 320x480 internal buffer (the same 153,600 bytes every gate diffs) & the window shows it width-doubled at 640x480, which is why the captures are 640x480. Higher resolution comes after, & only after, the frozen-scene diff is clean. The 320x480 build is the oracle that proves the high-res one. ## License The reconstructed source is Copyright 2026 Fred Angstadt, under the PolyForm Noncommercial License 1.0.0 (`LICENSE.md`): use it, change it, and share it for any noncommercial purpose. The original game and its assets (`reference/original/`, `assets/game.dat`, `dist/game.dat`) remain Copyright 1994 Safari Software and Patrick Aalto and are redistributed as freeware with Patrick Aalto's written permission of July 16, 2026. Commercial use of anything here requires his agreement first. `dist/SDL3.dll` is SDL 3.4.12 under the zlib license. Details in `NOTICE.md`.