status: starting · keys: space play/pause · ← → previous/next scene · J L ∓5 s · F fullscreen
What you are watching
73 million seconds is a 4½-minute PC demo released by the group Pulse in 1998. A demo is not a game or a video: it is a
real-time program whose only purpose is to impress with audiovisual effects, written to run on the hardware of the day (here a Pentium-class PC
with MMX under DOS) and synchronised to a tracker module. The original ships as 73MS.EXE (348 KB, packed with UPX) and a 3.3 MB
data archive 73MIL.SEC; the credits in the NFO read unreal, camel, echo, visualize, voomie, keyg — “lead my singer. good-looking handsome routines.”
This page runs a JavaScript re-implementation of that engine: the original script and assets are read as they are, every effect was re-written from the disassembly of the original code, and the music is the original XM module played by libopenmpt. Nothing is pre-rendered except what the original also pre-rendered (the “nebar” fly-through is a video stored in the archive).
The demo, scene by scene
The whole show is described by a 10 KB text script. Its time axis is not seconds but pattern order / row of the tracker module
(order row command), so the visuals stay glued to the music whatever the machine speed. Click a scene to jump to it.
- Glass sculptureorder 0 · scene, lowpass, palette, fade A 3D sculpture rendered into a 16-bit buffer, converted to 8-bit, smoothed and colourised through a palette, over a second scene in 32-bit colour.
- Radial blur & logoorder 3 · radialblur, fade, blend The logo pictures fade in while a zoom blur streaks the frame.
- Nebar fly-throughorder 5 · playrgbani A pre-rendered 40-frame 24-bit animation (6.9 MB unpacked, 1.5 MB in the archive) looped through the engine's animation player.
- P_U particlesorder 7 · scene, lowpass, radialblur, controlfloat Particle logo scene with feedback blur;
controlfloatevents from the script drive the beat. - Parasiteorder 11 · parasite An image-driven distortion effect fed with a PNG.
- Smoke & flaresorder 12 · scene, setupflares, flareparticles Particle smoke lit by five animated light sources, rendered with additive flare sprites into an 8-bit buffer.
- Marching cubesorder 15 · volume3D, metaballs, morphvolume, marchingcubes A voxel volume built from scanned shapes, metaballs and a plasma, polygonised every frame into a box object.
- Echo atomsorder 17 · echo, radialblur, palette A feedback “echo” effect fed by a 256×256 pattern, green palette, flash to white.
- Echo tunnelsorder 19 · echo6, lowpass, radialblur Another echo variant driven by an expression of time.
- Echo, matrix & blobsorder 23 · echo4, matrix, xfade A convolution (“matrix”) pass on an echo, two palettes cross-faded.
- Displaced sculptureorder 26 · displace247, colordistort A 3D object whose vertices are displaced, then colour-remapped through a distortion table.
- Medusaorder 29 · reebok A 256×256 indexed picture warped in 8-bit colour.
- Creditsorder 30 The credits picture fades in.
- Cubes & spritesorder 32 · volumesprites Metaball volume rendered as sprites through the camera of a 3D scene, until the end (order 35, row 33).
The retro context
1998: DOS is not dead yet
Windows 98 had just shipped, but the demoscene still wrote for MS-DOS: the program owns the machine, switches the graphics card to a mode of its choice
and talks to the hardware directly. Executables like this one were “32-bit protected-mode DOS programs”: a DOS extender (here DOS/4GW, bundled with the
Watcom C/C++32 compiler) switches the CPU to protected mode and gives the program a flat 4 GB address space, while DOS itself is still there for files and the keyboard.
The executable here is an LE image compressed with UPX; its start-up code decompresses 900 KB of code and data into memory, un-filters the
call instructions (UPX rewrites relative calls to absolute ones to help the compressor) and applies the relocations.
320×200, 32 bits per pixel — in 1998
Instead of the classic 256-colour VGA mode, this engine renders into buffers of 8, 16 or 32 bits per pixel (all 320×200 except one 256×256 texture) and
copies the result to the screen through VESA (the demo asks you to run UNIVBE if your card has no BIOS support). 8-bit buffers are used for effects
that only need an intensity channel — feedback, blur, echo — and are converted to true colour by a palette at the end; 32-bit buffers carry the pictures and 3D scenes.
Many inner loops use MMX, the integer SIMD extension of the Pentium MMX (1997): a switch -nommx exists, and cpuid is used to detect it.
Music: a tracker module
The soundtrack is tour2.xm, a FastTracker 2 “Extended Module”: 16 channels, 34 patterns of 64 rows, 24 instruments, 945 KB. The sound library is
IMS (Indoor Music System by Niklas Beisert, of the Cubic Team), which supported all the sound cards of the time — Gravis UltraSound, SoundBlaster, ESS, Windows Sound System… —
and gave the demo the current order and row of the song, the clock of the whole production.
Keeping it small, and fast
The archive 73MIL.SEC holds 45 files compressed with five home-made schemes (two LZ variants, a bzip-style block-sorting coder for the animation,
plus stored JPEG/PNG/TGA). The engine itself is Watcom C compiled code — you can see its stack-check prologue on every function —
assembled from a library of reusable effects: 57 effect types are registered in one table, only about 25 are used by this demo.
How the engine works
The architecture is data-driven: script.unr creates named buffers and then adds events. An event is an instance of an effect type with a
list of input buffers, a list of output buffers and a parameter string. Every frame the engine runs all events of layer 0, then layer 1, 2 and 3, in creation order. Effects can therefore be
chained like a patch-bay: a 3D scene renders into SCREEN16, an interpolator converts it to SCREEN8, a lowpass smooths it, a palette turns it into SCREEN (32-bit), a fade darkens it.
0 0 createBuffer SCREEN 320 200 32
createBuffer SCREEN16 320 200 16
createBuffer SCREEN8 320 200 8
loadscene aziz1.lwe SCREEN16 i
...
addEvent 0 scene skeen {SCREEN16} {SCREEN16} {aziz1.lwe 32.0 200.0}
addEvent 0 interpolator i {SCREEN16} {SCREEN8} {}
addEvent 1 lowpass lo {SCREEN8} {SCREEN8} {0.7}
addEvent 1 palette pal {SCREEN8} {SCREEN} {medusa.pal}
addEvent 1 fade fuckyou {SCREEN} {SCREEN} {0 0 0 1.0-clip10(Frame*0.003)}
Parameters can be expressions compiled by a small stack machine (sin, cos, fabs, sqrt, floor, clip, clip10, clip11…) and evaluated each frame with the variable
Frame: the time since the event was added, in hundredths of a second (the engine multiplies the player clock by 100). That is how a fade is written:
1.0-clip10(Frame*0.003) goes from 1 to 0 in 3.3 seconds. Script times are order row pairs of the module, so the scene changes land exactly on the beat.
Reverse-engineering the executable
- Unpacking. The EXE is a DOS/4GW “LE” executable with two objects; the second one holds a UPX 0.30 stub with a custom entry (the usual
signature checks are neutralised), so
upx -drefuses it. The stub was run in a CPU emulator (Unicorn) up to the point where it jumps to the program, the decompressed image was captured, the 8257 filteredcallopcodes were restored and the relocation stream (21 802 entries in four blocks: code, two small data objects and the main data object) was applied. The result is a flat 860 KB image whose strings, effect table and function boundaries can be read directly. - The archive.
73MIL.SECis a plain sequence of records (sizes, name, compression-type byte, payload). Its decompressors were located from the loader at0x6b25, and run as they are inside the emulator to extract all 45 files, including the 6.9 MB animation that needs the engine's own block-sort decoder. - The engine. Effects are registered in a 20-byte-record table at
0xbd2d0; each record points to a descriptor of three functions (init — parse parameters and compile expressions, run — per-frame work, free). Events, buffers and scenes are simple structs, documented inre/NOTES.mdof the repository. - Ground truth. A harness boots the original code inside the emulator (DOS, DPMI and VESA calls are stubbed), replaces the music clock by a virtual one and captures the screen buffer of each frame: the original engine renders headlessly and yields reference frames to compare the JavaScript effects against. Single functions can also be called directly with synthetic buffers, which is how the per-pixel loops were checked byte for byte.
Interesting inner loops
The effects below were ported from the disassembly. Each note shows the real x86 code of the hot loop (addresses refer to the unpacked image, base 0) next to the JavaScript equivalent.
loading notes…
About this port
- Engine:
js/engine.jsparses the script, schedules commands on the module's (order,row) timeline and runs the events;js/core.jsholds the buffer type and the expression compiler; effects live injs/fx/. - Music: the original
tour2.xmthrough chiptune3 / libopenmpt (BSD licences). The visual clock follows the audio clock; seeking replays the script and re-warms the feedback buffers for 1.5 s. - Differences: the original ran at the speed of the PC and used 16-bit/8-bit hardware modes; here frames are produced at the display refresh rate. Effects that depend on the previous frame (echo, blur) therefore look slightly different at other frame rates. Anything the port approximates is listed in the notes of the effect.
- Source and tools: the repository contains the reverse-engineering notes, the emulator harness and the tools used to extract the data (
re/,tools/). - Credits: demo, music and assets © Pulse (1998); this page is an educational, non-commercial study. Greetings to everyone who made the DOS scene.