Skip to content

Add a reproducible build and a browser-playable page - #1

Open
mpasternak wants to merge 13 commits into
matrix-toolbox:mainfrom
dtz-labs:add-build-and-browser-player
Open

Add a reproducible build and a browser-playable page#1
mpasternak wants to merge 13 commits into
matrix-toolbox:mainfrom
dtz-labs:add-build-and-browser-player

Conversation

@mpasternak

@mpasternak mpasternak commented Aug 16, 2026

Copy link
Copy Markdown

Hi! Thank you for this repository — the annotated disassembly is a genuinely lovely piece of work, and the reduction from 5649 to ~1.5 kB is remarkable.

▶ See it running first

Before reading any of the diff, you can just try it — this branch deployed to my fork's GitHub Pages:

https://dtz-labs.github.io/MARS.COM/

Click the screen to capture the mouse; moving it pans the camera. (That URL is only there so you can see it working — nothing in the PR points at my fork. If you merge this, the same workflow publishes it under your own account at https://matrix-toolbox.github.io/MARS.COM/.)


The repository ships the assembly source, but there is currently no build recipe and no way to see the demo without a DOS machine. This PR adds both. Everything here is optional and separable — please take only the parts you want, or none of them.

What it adds

A reproducible build (scripts/build.sh, make build)

MARS.ASM is MASM/TASM dialect (.model tiny, org 100h, COMMENT # blocks), so NASM cannot assemble it. The build uses JWasm, pinned by tag in versions.env. Because the model is tiny, the assembler emits the .COM image directly — no link step.

If no assembler is on PATH the script builds JWasm from source into a gitignored .toolchain/; --docker covers hosts with no compiler. JWasm's GccUnix.mak targets Linux/FreeBSD, so on macOS the script applies two portability fixes automatically (an include shim for <malloc.h>, and stripping the GNU-only -s / -Wl,-Map link flags).

MARS.ASM stays the single source of truth — the binary is never committed.

A browser-playable page (scripts/build-site.sh, GitHub Pages workflow)

Rather than emulating DOS, the page boots a real emulated PC with v86: the 720 kB FreeDOS floppy from v86's own demos, with MARS.COM, the CuteMouse driver and an AUTOEXEC.BAT injected at build time via mtools.

The mouse driver is not incidental — MARS calls int 33h, which DOSBox-based players implement internally but hardware emulation does not provide on its own. (MARS checks for the driver and degrades gracefully, so without it the landscape still renders and only the camera stops responding — nicely defensive code.)

I first tried a DOSBox-based player, but it crashed repeatedly in Firefox from inside the DOSBox core, so I moved to hardware emulation instead.

Every third-party download — the v86 runtime, both BIOS blobs, the FreeDOS image, the mouse driver — is pinned by SHA-256, and the BIOS blobs are pinned to an immutable commit rather than a branch. Nothing is loaded from a third-party origin at page-view time.

CI verifies the build and runs the test suite on every push.

One question for you

The README says 1517 bytes, but this toolchain reliably produces 1550 (sha256:10a1bb6c…), byte-identical under JWasm v2.20 and v2.21 and across macOS/clang/ARM64 and Linux/gcc. I documented both numbers rather than guess which is right — do you remember which assembler and options you used? I would happily match the build to reproduce your exact 1517 bytes.

Notes

  • .386 matters: there are 35 lines using 32-bit registers, so the binary needs a 386+. Worth stating explicitly somewhere, I think.
  • The Pages workflow needs Pages enabled with the "GitHub Actions" source; it does nothing until then.
  • Building the site needs mtools; building just the binary does not.
  • Original credit to Tim J. Clarke and your disassembly work are preserved in the page, the README and the licence.

Happy to split this into smaller PRs, drop the Pages part, or adjust anything to your taste.

mpasternak and others added 13 commits August 16, 2026 20:22
Records the approved design: JWasm-based reproducible build of MARS.ASM
into MARS.COM, and a GitHub Pages site that runs it via a self-hosted,
build-time-pinned js-dos 8.4.1.

Includes verified findings that shaped the design: MARS.ASM is MASM
dialect (not NASM), JWasm assembles it to 1550 bytes with no errors,
and js-dos 8.4.1 needs no SharedArrayBuffer, so it deploys on Pages
without COOP/COEP headers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Adds the task-by-task implementation plan, then fixes seven defects found
in a self-review of both documents:

Plan: corrected assertion counts in three tasks (they under- and
over-counted, which would send an executor hunting a phantom bug); closed
a hole where test-lib.sh recorded nothing when die() behaved correctly;
replaced a brittle basename check with a file-existence check so the repo
can be cloned to any directory name; and rewrote a self-referential note
about the third-party-origin assertion.

Spec: documented versions.env, scripts/lib.sh and tests/ which the plan
introduced but the spec never named; replaced the ambiguous "under 64 KB"
with the true .COM ceiling of 65280 bytes (0xFF00, PSP-adjusted); and
aligned the verification section with the plan, which enforces the pinned
size and SHA-256 in CI rather than merely recording them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Resolves an assembler from $JWASM, then PATH, then builds JWasm v2.20
from source into .toolchain/. A --docker flag covers hosts with no
compiler, and --check enforces the pinned size and SHA-256.

JWasm's GccUnix.mak targets Linux/FreeBSD and does not build on macOS,
which the plan did not anticipate. Two fixes are applied automatically on
Darwin: an include shim so <malloc.h> resolves to <stdlib.h> (injected via
inc_dirs rather than editing 58 source files), and removal of the GNU-only
-s and -Wl,-Map link flags that Apple's ld rejects. Both are confined to
the gitignored .toolchain/ checkout.

The macOS/clang/ARM64 build produces a MARS.COM byte-identical to the
Linux/gcc/aarch64 build, so the pinned SHA is a real reproducibility gate
rather than a description of one machine.

Build output now goes to a log file instead of /dev/null, so a failure is
diagnosable without re-running the build by hand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
The bundle is a ZIP whose root becomes drive C:. js-dos does not
auto-mount, so [autoexec] performs the mount itself, matching the layout
js-dos's own bundle generator emits.

machine=vgaonly is pinned because the renderer drives VGA mode 13h and
reprograms the palette DAC; DOSBox's default svga_s3 shows artifacts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Downloads the js-dos npm tarball during the build and extracts only the
five files the page needs, so the repo stays lean and the deployed page
serves everything from its own origin with no CDN dependency at view time.

Ships the plain dosbox backend and skips dosbox-x, cutting the staged
payload from roughly 17 MB to 2.0 MB. MARS only needs machine=vgaonly,
which plain DOSBox supports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Composes _site/ from the built binary, the .jsdos bundle, the fetched
emulator assets and the static page sources, so nothing is loaded from a
third-party origin at view time. A test asserts that invariant directly.

Adds .nojekyll because GitHub Pages otherwise runs Jekyll, which strips
dot-prefixed paths and would mangle the asset directories.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Browser testing revealed the asset subset was too aggressive: js-dos
unpacks the .jsdos bundle (a ZIP) with wlibzip at runtime, so omitting
wlibzip.js and wlibzip.wasm made the emulator 404 before DOS could boot.
Both are now fetched and asserted by the test.

serve.sh builds and serves over HTTP because js-dos loads the bundle with
fetch(), which browsers block on file:// origins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Adds a CI workflow that runs build.sh --check plus the test suite on every
push and PR, and a Pages workflow that builds the site and deploys it.

Documents the build, the browser player, the macOS caveat, and the 33-byte
gap between upstream's reported 1517 bytes and this build's 1550.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Adds a reproducible JWasm build of MARS.ASM and a GitHub Pages site that
runs the resulting MARS.COM in the browser via a self-hosted js-dos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
js-dos crashed repeatedly in Firefox with "Backend crashed, cause: ...",
which comes from the Emscripten DOSBox module's exception handler in
wdosbox.js — the DOSBox core itself was throwing. Switching to em-dosbox
or DOSee would not have helped: they wrap the same DOSBox core.

v86 emulates real PC hardware instead of emulating the DOS API, so it has
entirely different failure modes, and its Pentium-level instruction set
covers the 32-bit code MARS needs (MARS.ASM declares .386 and uses 32-bit
registers on 35 lines).

The page now boots the 720 KB FreeDOS floppy from v86's own demos, with
MARS.COM, CuteMouse and an AUTOEXEC.BAT injected at build time via mtools.
The mouse driver is required because MARS calls int 33h, a service DOSBox
provided internally but hardware emulation does not; MARS checks for the
driver and degrades gracefully, so a missing driver costs camera control
rather than the whole demo.

Both external downloads are checksum-pinned so a rebuild either reproduces
the same image or fails loudly.

Also adds a favicon, which was the last remaining 404 on the page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
A push-time security review flagged fetch-v86.sh for supply-chain
integrity, correctly: it pulled seabios.bin and vgabios.bin from the
"master" branch of copy/v86 with no verification at all, while
make-image.sh already checksum-pinned both of its downloads. The BIOS
blobs are executed by the emulator, and a mutable branch ref can change
under us at any time.

Pins V86_BIOS_REF to an immutable commit and adds SHA-256 verification for
both BIOS blobs and the v86 npm tarball, so every artifact that is fetched
and then executed is pinned by content rather than by name.

Hoists verify_sha into scripts/lib.sh so the two fetchers share one
implementation instead of make-image.sh carrying a private copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Cv1JoZyJdqYiCXiVSKDQP
Adds a build that assembles MARS.ASM into MARS.COM, and an optional
GitHub Pages site that runs the result in a browser.

The build uses JWasm, pinned by tag, because MARS.ASM is MASM/TASM dialect
and NASM cannot assemble it. Output is a 1550-byte MARS.COM, byte-identical
under JWasm v2.20 and v2.21 and across macOS/clang/ARM64 and Linux/gcc.
MARS.ASM stays the single source of truth; the binary is never committed.

The page boots a real emulated PC via v86 rather than emulating DOS: the
720 KB FreeDOS floppy from v86's own demos, with MARS.COM, the CuteMouse
driver and an AUTOEXEC.BAT injected at build time. The mouse driver is
needed because MARS calls int 33h, which hardware emulation does not
provide on its own. Every third-party download is checksum-pinned.
@matrix-toolbox

Copy link
Copy Markdown
Owner

Hi, nice browser version! MARS has been reborn once again. The build recipe is simply the original assembly source code, followed by: C:\> TASM MARS.ASM and then: C:\> TLINK /T MARS.OBJ to create MARS.COM under DOS. So NASM will not do the job here. I have never tried FASM, though. However, using original 80386 hardware and/or DOSBox, it is possible to reproduce the exact 1517B executable. I still have access to some old machines and I will try to make a short video of the compilation process this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants