<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TinyComputers.io (Posts about wasm)</title><link>https://tinycomputers.io/</link><description></description><atom:link href="https://tinycomputers.io/categories/wasm.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 A.C. Jokela 
&lt;!-- div style="width: 100%" --&gt;
&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /&gt; Creative Commons Attribution-ShareAlike&lt;/a&gt;&amp;nbsp;|&amp;nbsp;
&lt;!-- /div --&gt;
</copyright><lastBuildDate>Wed, 11 Mar 2026 00:05:43 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Building a Browser-Based Z80 Emulator for the RetroShield</title><link>https://tinycomputers.io/posts/browser-based-z80-emulator-retroshield.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;p&gt;There's something deeply satisfying about running code on vintage hardware. The blinking cursor, the deliberate pace of execution, the direct connection between your keystrokes and the machine's response. The &lt;a href="https://baud.rs/zSCNiC"&gt;RetroShield&lt;/a&gt; by Erturk Kocalar brings this experience to modern makers by allowing real vintage CPUs like the Zilog Z80 to run on Arduino boards. But what if you could experience that same feeling directly in your web browser?&lt;/p&gt;
&lt;p&gt;That's exactly what I set out to build: a complete Z80 emulator that runs RetroShield firmware in WebAssembly, complete with authentic CRT visual effects and support for multiple programming language interpreters.&lt;/p&gt;
&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/browser-based-z80-emulator-retroshield_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;32 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;Try It Now&lt;/h3&gt;
&lt;p&gt;Select a ROM below and click "Load ROM" to start. Click on the terminal to focus it, then type to interact with the interpreter.&lt;/p&gt;
&lt;style&gt;
@font-face {
    font-family: 'Glass TTY VT220';
    src: url('https://tinycomputers.io/z80-emulator/Glass_TTY_VT220.woff') format('woff'),
         url('https://tinycomputers.io/z80-emulator/Glass_TTY_VT220.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
}

.emulator-container {
    max-width: 900px;
    margin: 0 auto;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

.control-panel {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    margin-bottom: 15px;
    padding: 15px;
    background: #f5f5f5;
    border-radius: 8px;
}

.control-panel label {
    font-weight: 600;
    margin-right: 5px;
}

.control-panel select {
    padding: 8px 12px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    background: white;
    min-width: 200px;
}

.control-panel button {
    padding: 8px 16px;
    font-size: 14px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.2s;
}

.btn-primary {
    background: #007bff;
    color: white;
}

.btn-primary:hover {
    background: #0056b3;
}

.btn-secondary {
    background: #6c757d;
    color: white;
}

.btn-secondary:hover {
    background: #545b62;
}

.crt-container {
    position: relative;
    border-radius: 20px;
    overflow: hidden;
    box-shadow: 0 0 40px rgba(0, 255, 0, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.5);
}

#terminal {
    background: #0a0a0a;
    color: #00ff00;
    font-family: 'Glass TTY VT220', "Courier New", Courier, monospace;
    font-size: 18px;
    line-height: 1.4;
    padding: 20px;
    border-radius: 20px;
    height: 400px;
    overflow-y: auto;
    white-space: pre-wrap;
    word-wrap: break-word;
    border: none;
    text-shadow: 0 0 5px rgba(0, 255, 0, 0.5), 0 0 10px rgba(0, 255, 0, 0.3);
    animation: textShadow 1.6s infinite;
    position: relative;
}

#terminal:focus {
    outline: none;
}

.crt-container::before {
    content: " ";
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%),
                linear-gradient(90deg, rgba(255, 0, 0, 0.03), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.03));
    z-index: 2;
    background-size: 100% 4px, 3px 100%;
    pointer-events: none;
    border-radius: 20px;
}

.crt-container::after {
    content: " ";
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: rgba(18, 16, 16, 0.1);
    opacity: 0;
    z-index: 2;
    pointer-events: none;
    animation: flicker 0.15s infinite;
    border-radius: 20px;
}

.vignette {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: radial-gradient(ellipse at center, transparent 0%, transparent 60%, rgba(0, 0, 0, 0.6) 100%);
    pointer-events: none;
    z-index: 3;
    border-radius: 20px;
}

@keyframes flicker {
    0% { opacity: 0.27861; }
    5% { opacity: 0.34769; }
    10% { opacity: 0.23604; }
    15% { opacity: 0.90626; }
    20% { opacity: 0.18128; }
    25% { opacity: 0.83891; }
    30% { opacity: 0.65583; }
    35% { opacity: 0.67807; }
    40% { opacity: 0.26559; }
    45% { opacity: 0.84693; }
    50% { opacity: 0.96019; }
    55% { opacity: 0.08594; }
    60% { opacity: 0.20313; }
    65% { opacity: 0.71988; }
    70% { opacity: 0.53455; }
    75% { opacity: 0.37288; }
    80% { opacity: 0.71428; }
    85% { opacity: 0.70419; }
    90% { opacity: 0.7003; }
    95% { opacity: 0.36108; }
    100% { opacity: 0.24387; }
}

@keyframes textShadow {
    0% { text-shadow: 0.4px 0 1px rgba(0,30,255,0.5), -0.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    5% { text-shadow: 2.8px 0 1px rgba(0,30,255,0.5), -2.8px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    10% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    15% { text-shadow: 0.4px 0 1px rgba(0,30,255,0.5), -0.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    20% { text-shadow: 3.5px 0 1px rgba(0,30,255,0.5), -3.5px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    25% { text-shadow: 1.6px 0 1px rgba(0,30,255,0.5), -1.6px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    30% { text-shadow: 0.7px 0 1px rgba(0,30,255,0.5), -0.7px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    35% { text-shadow: 3.9px 0 1px rgba(0,30,255,0.5), -3.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    40% { text-shadow: 3.9px 0 1px rgba(0,30,255,0.5), -3.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    45% { text-shadow: 2.2px 0 1px rgba(0,30,255,0.5), -2.2px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    50% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    55% { text-shadow: 2.4px 0 1px rgba(0,30,255,0.5), -2.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    60% { text-shadow: 2.2px 0 1px rgba(0,30,255,0.5), -2.2px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    65% { text-shadow: 2.9px 0 1px rgba(0,30,255,0.5), -2.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    70% { text-shadow: 0.5px 0 1px rgba(0,30,255,0.5), -0.5px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    75% { text-shadow: 1.9px 0 1px rgba(0,30,255,0.5), -1.9px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    80% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    85% { text-shadow: 0.1px 0 1px rgba(0,30,255,0.5), -0.1px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    90% { text-shadow: 3.4px 0 1px rgba(0,30,255,0.5), -3.4px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    95% { text-shadow: 2.2px 0 1px rgba(0,30,255,0.5), -2.2px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
    100% { text-shadow: 2.6px 0 1px rgba(0,30,255,0.5), -2.6px 0 1px rgba(255,0,80,0.3), 0 0 5px rgba(0,255,0,0.5); }
}

.scanline {
    position: absolute;
    width: 100%;
    height: 2px;
    background: rgba(0, 255, 0, 0.1);
    z-index: 4;
    pointer-events: none;
    animation: scanlineMove 8s linear infinite;
}

@keyframes scanlineMove {
    0% { top: 0%; }
    100% { top: 100%; }
}

.status-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 15px;
    background: #333;
    color: #fff;
    border-radius: 0 0 8px 8px;
    font-size: 13px;
    font-family: monospace;
    margin-top: -8px;
}

.status-left, .status-right {
    display: flex;
    gap: 20px;
}

.status-item {
    display: flex;
    align-items: center;
    gap: 5px;
}

.status-indicator {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #666;
}

.status-indicator.running {
    background: #00ff00;
    box-shadow: 0 0 5px #00ff00;
}

.status-indicator.halted {
    background: #ff0000;
}

.rom-info {
    margin-top: 20px;
    padding: 15px;
    background: #e9ecef;
    border-radius: 8px;
}

.rom-info h4 {
    margin-top: 0;
    margin-bottom: 10px;
}

.rom-info p {
    margin: 5px 0;
    font-size: 14px;
}

.keyboard-hint {
    margin-top: 15px;
    padding: 10px 15px;
    background: #fff3cd;
    border-radius: 4px;
    font-size: 13px;
    color: #856404;
}

#rom-name { font-weight: bold; }
#rom-tips { font-style: italic; margin-top: 10px; }

#cursor {
    display: inline;
    background-color: #00ff00;
    animation: blink 1s step-end infinite;
}

@keyframes blink {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}
&lt;/style&gt;

&lt;div class="emulator-container"&gt;
    &lt;div class="control-panel"&gt;
        &lt;label for="rom-select"&gt;ROM:&lt;/label&gt;
        &lt;select id="rom-select"&gt;
            &lt;option value=""&gt;-- Select a ROM --&lt;/option&gt;
            &lt;option value="fortran77.bin" selected&gt;Fortran 77 Interpreter&lt;/option&gt;
            &lt;option value="mint.z80.bin"&gt;MINT (Minimal Interpreter)&lt;/option&gt;
            &lt;option value="firth.z80.bin"&gt;Firth Forth&lt;/option&gt;
            &lt;option value="monty.z80.bin"&gt;Monty&lt;/option&gt;
            &lt;option value="pascal.bin"&gt;Retro Pascal&lt;/option&gt;
            &lt;option value="basic_gs47b.bin"&gt;Grant Searle BASIC&lt;/option&gt;
            &lt;option value="efex.bin"&gt;EFEX Monitor&lt;/option&gt;
        &lt;/select&gt;
        &lt;button id="btn-load" class="btn-primary"&gt;Load ROM&lt;/button&gt;
        &lt;button id="btn-reset" class="btn-secondary"&gt;Reset&lt;/button&gt;
        &lt;button id="btn-clear" class="btn-secondary"&gt;Clear&lt;/button&gt;
    &lt;/div&gt;
    &lt;div class="crt-container"&gt;
        &lt;div id="terminal" tabindex="0"&gt;&lt;span id="terminal-content"&gt;&lt;/span&gt;&lt;span id="cursor"&gt; &lt;/span&gt;&lt;/div&gt;
        &lt;div class="vignette"&gt;&lt;/div&gt;
        &lt;div class="scanline"&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="status-bar"&gt;
        &lt;div class="status-left"&gt;
            &lt;div class="status-item"&gt;
                &lt;span class="status-indicator" id="status-indicator"&gt;&lt;/span&gt;
                &lt;span id="status-text"&gt;Idle&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="status-item"&gt;
                &lt;span&gt;PC:&lt;/span&gt;
                &lt;span id="status-pc"&gt;0000&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="status-right"&gt;
            &lt;div class="status-item"&gt;
                &lt;span&gt;Cycles:&lt;/span&gt;
                &lt;span id="status-cycles"&gt;0&lt;/span&gt;
            &lt;/div&gt;
            &lt;div class="status-item"&gt;
                &lt;span&gt;Speed:&lt;/span&gt;
                &lt;span id="status-speed"&gt;0 MHz&lt;/span&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="keyboard-hint"&gt;
        &lt;strong&gt;Tip:&lt;/strong&gt; Click on the terminal to focus it, then type to send input. Try loading Fortran 77 and entering: &lt;code&gt;INTEGER X&lt;/code&gt; then &lt;code&gt;X = 42&lt;/code&gt; then &lt;code&gt;WRITE(*,*) X&lt;/code&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class="rom-info" id="rom-info"&gt;
    &lt;h5&gt;ROM Information&lt;/h5&gt;
    &lt;p id="rom-name"&gt;&lt;/p&gt;
    &lt;p id="rom-description"&gt;Select a ROM above to load it into the emulator.&lt;/p&gt;
    &lt;p id="rom-tips"&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;script type="module"&gt;
import init, { Z80Emulator } from '/z80-emulator/retro_z80_emulator.js';

const ROM_INFO = {
    'fortran77.bin': {
        name: 'Fortran 77 Interpreter',
        description: 'A Fortran 77 subset interpreter with BCD floating point, DO loops, IF/THEN/ELSE, arrays, and SUBROUTINE/FUNCTION support.',
        serial: 'acia',
        tips: 'Type PROGRAM to start a program, END to finish, then RUN to execute. Try: INTEGER X followed by X = 42 then WRITE(*,*) X'
    },
    'mint.z80.bin': {
        name: 'MINT (Minimal Interpreter)',
        description: 'A minimal stack-based interpreter similar to Forth. Very compact and efficient.',
        serial: 'acia',
        tips: 'MINT uses single-character commands. Try: 1 2 + . (adds 1+2 and prints result)'
    },
    'firth.z80.bin': {
        name: 'Firth Forth',
        description: 'A Forth interpreter by John Hardy. Full Forth implementation with standard words.',
        serial: 'acia',
        tips: 'Standard Forth commands. Try: 2 3 * . (multiplies 2*3 and prints 6)'
    },
    'monty.z80.bin': {
        name: 'Monty',
        description: 'An interpreted language by John Hardy inspired by Python and Forth.',
        serial: 'acia',
        tips: 'Try: print "Hello World" or basic arithmetic expressions.'
    },
    'pascal.bin': {
        name: 'Retro Pascal',
        description: 'A minimal Pascal interpreter in expression evaluation mode.',
        serial: 'acia',
        tips: 'Type arithmetic expressions: 2 + 3 or 10 * 4 + 2. Boolean: TRUE AND FALSE'
    },
    'basic_gs47b.bin': {
        name: 'Grant Searle BASIC',
        description: 'Microsoft BASIC adapted by Grant Searle.',
        serial: '8251',
        tips: 'Standard BASIC. Try: PRINT 2+2 or write a program with line numbers.'
    },
    'efex.bin': {
        name: 'EFEX Monitor',
        description: 'A simple machine code monitor for examining and modifying memory.',
        serial: '8251',
        tips: 'Type ? for help. Use D to dump memory, E to examine/edit.'
    }
};

let emulator = null;
let running = false;
let animationId = null;
let lastCycles = 0;
let lastTime = 0;

const terminal = document.getElementById('terminal');
const terminalContent = document.getElementById('terminal-content');
const cursor = document.getElementById('cursor');
const romSelect = document.getElementById('rom-select');
const btnLoad = document.getElementById('btn-load');
const btnReset = document.getElementById('btn-reset');
const btnClear = document.getElementById('btn-clear');
const statusIndicator = document.getElementById('status-indicator');
const statusText = document.getElementById('status-text');
const statusPC = document.getElementById('status-pc');
const statusCycles = document.getElementById('status-cycles');
const statusSpeed = document.getElementById('status-speed');
const romName = document.getElementById('rom-name');
const romDescription = document.getElementById('rom-description');
const romTips = document.getElementById('rom-tips');

function appendOutput(text) {
    let output = '';
    for (const char of text) {
        const code = char.charCodeAt(0);
        if (code === 13) {
        } else if (code === 10) {
            output += '\n';
        } else if (code === 8) {
            if (terminalContent.textContent.length &gt; 0) {
                terminalContent.textContent = terminalContent.textContent.slice(0, -1);
            }
        } else if (code &gt;= 32 &amp;&amp; code &lt; 127) {
            output += char;
        }
    }
    terminalContent.textContent += output;
    terminal.scrollTop = terminal.scrollHeight;
}

function updateStatus() {
    if (!emulator) return;
    const pc = emulator.get_pc();
    const cycles = emulator.get_cycles();
    const halted = emulator.is_halted();
    statusPC.textContent = pc.toString(16).toUpperCase().padStart(4, '0');
    statusCycles.textContent = cycles.toLocaleString();
    if (halted) {
        statusIndicator.className = 'status-indicator halted';
        statusText.textContent = 'Halted';
        running = false;
    } else if (running) {
        statusIndicator.className = 'status-indicator running';
        statusText.textContent = 'Running';
    } else {
        statusIndicator.className = 'status-indicator';
        statusText.textContent = 'Idle';
    }
    const now = performance.now();
    if (lastTime &gt; 0) {
        const elapsed = (now - lastTime) / 1000;
        const cyclesDelta = Number(cycles) - Number(lastCycles);
        if (elapsed &gt; 0) {
            const mhz = (cyclesDelta / elapsed / 1000000).toFixed(2);
            statusSpeed.textContent = mhz + ' MHz';
        }
    }
    lastCycles = cycles;
    lastTime = now;
}

function runLoop() {
    if (!running || !emulator) return;
    emulator.run(50000);
    const output = emulator.get_output_string();
    if (output.length &gt; 0) {
        appendOutput(output);
    }
    updateStatus();
    if (!emulator.is_halted()) {
        animationId = requestAnimationFrame(runLoop);
    }
}

async function loadRom(filename) {
    if (!emulator) return;
    try {
        terminalContent.textContent = 'Loading ' + filename + '...\n';
        const response = await fetch('/z80-emulator/roms/' + filename);
        if (!response.ok) {
            throw new Error('Failed to load ROM: ' + response.statusText);
        }
        const buffer = await response.arrayBuffer();
        const data = new Uint8Array(buffer);
        emulator.load_rom(data);
        const info = ROM_INFO[filename];
        if (info &amp;&amp; info.serial === '8251') {
            emulator.set_8251_mode(true);
        } else {
            emulator.set_8251_mode(false);
        }
        terminalContent.textContent = '';
        running = true;
        lastCycles = 0;
        lastTime = 0;
        runLoop();
        if (info) {
            romName.textContent = info.name;
            romDescription.textContent = info.description;
            romTips.textContent = 'Tips: ' + info.tips;
        }
    } catch (err) {
        terminalContent.textContent += 'Error: ' + err.message + '\n';
        console.error(err);
    }
}

btnLoad.addEventListener('click', function() {
    const rom = romSelect.value;
    if (rom) {
        if (animationId) {
            cancelAnimationFrame(animationId);
        }
        running = false;
        loadRom(rom);
    }
});

btnReset.addEventListener('click', function() {
    if (emulator) {
        if (animationId) {
            cancelAnimationFrame(animationId);
        }
        emulator.reset();
        terminalContent.textContent = '';
        running = true;
        lastCycles = 0;
        lastTime = 0;
        runLoop();
    }
});

btnClear.addEventListener('click', function() {
    terminalContent.textContent = '';
});

terminal.addEventListener('keydown', function(e) {
    if (!emulator || !running) return;
    if (e.key === 'Enter') {
        emulator.send_char(13);
        e.preventDefault();
    } else if (e.key === 'Backspace') {
        emulator.send_char(8);
        e.preventDefault();
    } else if (e.key.length === 1) {
        emulator.send_char(e.key.charCodeAt(0));
        e.preventDefault();
    }
});

terminal.addEventListener('paste', function(e) {
    if (!emulator || !running) return;
    const text = e.clipboardData.getData('text');
    emulator.send_string(text);
    e.preventDefault();
});

async function main() {
    try {
        await init();
        emulator = new Z80Emulator();
        terminalContent.textContent = 'Z80 Emulator ready. Select a ROM and click "Load ROM" to begin.\n';
        updateStatus();
    } catch (err) {
        terminalContent.textContent = 'Failed to initialize emulator: ' + err.message + '\n';
        console.error(err);
    }
}

main();
&lt;/script&gt;

&lt;hr&gt;
&lt;h3&gt;The RetroShield Platform&lt;/h3&gt;
&lt;p&gt;Before diving into the emulator, it's worth understanding what makes the RetroShield special. Unlike software emulators that simulate a CPU in code, the RetroShield uses a &lt;em&gt;real&lt;/em&gt; vintage microprocessor. The Z80 variant features an actual Zilog Z80 chip running at its native speed, connected to an Arduino Mega or Teensy that provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Memory emulation&lt;/strong&gt;: The Arduino's SRAM serves as the Z80's RAM, while program code is stored in the Arduino's flash memory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I/O peripherals&lt;/strong&gt;: Serial communication, typically through an emulated MC6850 ACIA or Intel 8251 USART&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clock generation&lt;/strong&gt;: The Arduino provides the clock signal to the Z80&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This hybrid approach means you get authentic Z80 behavior - every timing quirk, every undocumented opcode - while still having the convenience of USB connectivity and easy program loading.&lt;/p&gt;
&lt;p&gt;The RetroShield is &lt;a href="https://baud.rs/dqAGfn"&gt;open source hardware&lt;/a&gt; and available on &lt;a href="https://baud.rs/7HAxS8"&gt;Tindie&lt;/a&gt;. For larger programs, the &lt;a href="https://baud.rs/ywxYHT"&gt;Teensy adapter&lt;/a&gt; expands available RAM from about 4KB to 256KB.&lt;/p&gt;
&lt;h4&gt;The Hardware Up Close&lt;/h4&gt;
&lt;p&gt;Here's my RetroShield Z80 setup with the Teensy adapter:&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 with Teensy adapter - overhead view" src="https://tinycomputers.io/images/retroshield2/IMG_4141.jpg" title="RetroShield Z80 mounted on Teensy adapter board"&gt;&lt;/p&gt;
&lt;p&gt;The Zilog Z80 CPU sits in the 40-pin DIP socket, with the Teensy 4.1 providing memory emulation and I/O handling beneath.&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 - angled view showing the Z80 chip" src="https://tinycomputers.io/images/retroshield2/IMG_4142.jpg" title="Close-up of the Z80 CPU in its socket"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 - side profile" src="https://tinycomputers.io/images/retroshield2/IMG_4143.jpg" title="Side view of the RetroShield stack"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="RetroShield Z80 - full assembly" src="https://tinycomputers.io/images/retroshield2/IMG_4144.jpg" title="Complete RetroShield Z80 assembly"&gt;&lt;/p&gt;
&lt;p&gt;The physical hardware runs identically to the browser emulator above - the same ROMs, the same interpreters, the same authentic Z80 execution.&lt;/p&gt;
&lt;h3&gt;Why Build a Browser Emulator?&lt;/h3&gt;
&lt;p&gt;Having built several interpreters and tools for the RetroShield, I found myself constantly cycling through the development loop: edit code, compile, flash to Arduino, test, repeat. A software emulator would speed this up significantly, but I also wanted something I could share with others who might not have the hardware.&lt;/p&gt;
&lt;p&gt;WebAssembly seemed like the perfect solution. It runs at near-native speed in any modern browser, requires no installation, and can be embedded directly in a web page. Someone curious about retro computing could try out a Fortran 77 interpreter or Forth environment without buying any hardware.&lt;/p&gt;
&lt;h3&gt;Building the Emulator in Rust&lt;/h3&gt;
&lt;p&gt;I chose Rust for the emulator implementation for several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Excellent WASM support&lt;/strong&gt;: Rust's &lt;code&gt;wasm-bindgen&lt;/code&gt; and &lt;code&gt;wasm-pack&lt;/code&gt; tools make compiling to WebAssembly straightforward&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Rust compiles to efficient code, important for cycle-accurate emulation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The rz80 crate&lt;/strong&gt;: Andre Weissflog's &lt;a href="https://baud.rs/ST34BV"&gt;rz80&lt;/a&gt; provides a battle-tested Z80 core&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The emulator architecture is straightforward:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="err"&gt;┌─────────────────────────────────────────────────┐&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Browser&lt;/span&gt;&lt;span class="w"&gt;                      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌───────────────────────────────────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;JavaScript&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;HTML&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Terminal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CRT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;effects&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Keyboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handling&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────────────┬─────────────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wasm&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;bindgen&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────────────▼─────────────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="n"&gt;Rust&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;WebAssembly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────────────┐&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;rz80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CPU&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="n"&gt;KB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Emulation&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;ROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RAM&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────────────┘&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;┌─────────────────────────────────────┐&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Emulation&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MC6850&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ACIA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="o"&gt;/$&lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Intel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8251&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;USART&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="o"&gt;/$&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└─────────────────────────────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;└───────────────────────────────────────────┘&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;
&lt;span class="err"&gt;└─────────────────────────────────────────────────┘&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Dual Serial Chip Support&lt;/h4&gt;
&lt;p&gt;One challenge was supporting ROMs that use different serial chips. The RetroShield ecosystem has two common configurations:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MC6850 ACIA&lt;/strong&gt; (ports $80/$81): Used by many homebrew projects including MINT, Firth Forth, and my own Fortran and Pascal interpreters. The ACIA has four registers (control, status, transmit data, receive data) mapped to two ports, with separate read/write functions per port.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Intel 8251 USART&lt;/strong&gt; (ports $00/$01): Used by Grant Searle's popular BASIC port and the EFEX monitor. The 8251 is simpler with just two ports - one for data and one for control/status.&lt;/p&gt;
&lt;p&gt;The emulator detects which chip to use based on ROM metadata and configures the I/O handlers accordingly.&lt;/p&gt;
&lt;h4&gt;Memory Layout&lt;/h4&gt;
&lt;p&gt;The standard RetroShield memory map looks like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Address Range&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$0000-$7FFF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32KB&lt;/td&gt;
&lt;td&gt;ROM/RAM (program dependent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$8000-$FFFF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32KB&lt;/td&gt;
&lt;td&gt;Extended RAM (Teensy adapter)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most of my interpreters use a layout where code occupies the lower addresses and data/stack occupy higher memory. The Fortran interpreter, for example, places its program text storage at $6700 and variable storage at $7200, with the stack growing down from $8000.&lt;/p&gt;
&lt;h3&gt;The CRT Effect&lt;/h3&gt;
&lt;p&gt;No retro computing experience would be complete without the warm glow of a CRT monitor. I implemented several visual effects using pure CSS:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scanlines&lt;/strong&gt;: A repeating gradient overlay creates the horizontal line pattern characteristic of CRT displays:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;crt-container&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Chromatic aberration&lt;/strong&gt;: CRT displays have slight color fringing due to the electron beam hitting phosphors at angles. I simulate this with animated text shadows that shift red and blue components:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;keyframes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;textShadow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="mf"&gt;-0.4&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* ... animation continues */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Flicker&lt;/strong&gt;: Real CRTs had subtle brightness variations. A randomized opacity animation creates this effect without being distracting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vignette&lt;/strong&gt;: The edges of CRT screens were typically darker than the center, simulated with a radial gradient.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The font&lt;/strong&gt;: I'm using the &lt;a href="https://baud.rs/vWwOWL"&gt;Glass TTY VT220&lt;/a&gt; font, a faithful recreation of the DEC VT220 terminal font from the 1980s. It's public domain and adds significant authenticity to the experience.&lt;/p&gt;
&lt;h3&gt;The Language Interpreters&lt;/h3&gt;
&lt;p&gt;The emulator comes pre-loaded with several language interpreters, each running as native Z80 code:&lt;/p&gt;
&lt;h4&gt;Fortran 77 Interpreter&lt;/h4&gt;
&lt;p&gt;This is my most ambitious RetroShield project: a subset of Fortran 77 running interpretively on an 8-bit CPU. It supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REAL numbers&lt;/strong&gt; via BCD (Binary Coded Decimal) floating point with 8 significant digits&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INTEGER and REAL variables&lt;/strong&gt; with implicit typing (I-N are integers)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arrays&lt;/strong&gt; up to 3 dimensions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DO loops&lt;/strong&gt; with optional STEP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block IF/THEN/ELSE/ENDIF&lt;/strong&gt; statements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SUBROUTINE and FUNCTION&lt;/strong&gt; subprograms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intrinsic functions&lt;/strong&gt;: ABS, MOD, INT, REAL, SQRT&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's a sample session:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;FORTRAN&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;77&lt;/span&gt; &lt;span class="n"&gt;Interpreter&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;RetroShield&lt;/span&gt; &lt;span class="n"&gt;Z80&lt;/span&gt;
&lt;span class="n"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PROGRAM&lt;/span&gt; &lt;span class="n"&gt;FACTORIAL&lt;/span&gt;
  &lt;span class="kt"&gt;INTEGER&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;
  &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
  &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="kr"&gt;DO&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;
  &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;CONTINUE&lt;/span&gt;
  &lt;span class="n"&gt;WRITE&lt;/span&gt;&lt;span class="cm"&gt;(*,*)&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'! ='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;
  &lt;span class="kr"&gt;END&lt;/span&gt;
&lt;span class="n"&gt;Program&lt;/span&gt; &lt;span class="n"&gt;entered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;RUN&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RUN&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5040&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The interpreter is written in C and cross-compiled with SDCC. At roughly 21KB of code, it pushes the limits of what's practical on the base RetroShield, which is why it requires the Teensy adapter.&lt;/p&gt;
&lt;h4&gt;MINT (Minimal Interpreter)&lt;/h4&gt;
&lt;p&gt;MINT is a wonderfully compact stack-based language. Each command is a single character, making it incredibly memory-efficient:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;1 2 + .&lt;/span&gt;
3
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;: SQ D * ;&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;5 SQ .&lt;/span&gt;
25
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Firth Forth&lt;/h4&gt;
&lt;p&gt;A full Forth implementation by John Hardy. Forth's stack-based paradigm and extensibility made it popular on memory-constrained systems:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;: FACTORIAL ( n -- n! ) 1 SWAP 1+ 1 DO I * LOOP ;&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;7 FACTORIAL .&lt;/span&gt;
5040
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Grant Searle's BASIC&lt;/h4&gt;
&lt;p&gt;A port of Microsoft BASIC that provides the classic BASIC experience:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;Z80 BASIC Ver 4.7b
Ok
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;10 FOR I = 1 TO 10&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;20 PRINT I * I&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;30 NEXT I&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;RUN&lt;/span&gt;
1
4
9
...
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Technical Challenges&lt;/h3&gt;
&lt;p&gt;Building this project involved solving several interesting problems:&lt;/p&gt;
&lt;h4&gt;Memory Layout Debugging&lt;/h4&gt;
&lt;p&gt;The Fortran interpreter crashed mysteriously when entering lines with statement labels. After much investigation, I discovered the CODE section had grown to overlap with the DATA section. The linker was told to place data at $5000, but code had grown past that point. The fix was updating the memory layout to give code more room:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c"&gt;# Before: code overlapped data&lt;/span&gt;
&lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--data-loc&lt;span class="w"&gt; &lt;/span&gt;0x5000

&lt;span class="c"&gt;# After: proper separation&lt;/span&gt;
&lt;span class="nv"&gt;LDFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--data-loc&lt;span class="w"&gt; &lt;/span&gt;0x5500
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This kind of bug is particularly insidious because it works fine until the code grows past a certain threshold.&lt;/p&gt;
&lt;h4&gt;BCD Floating Point&lt;/h4&gt;
&lt;p&gt;Implementing floating-point math on a Z80 without hardware support is challenging. I chose BCD (Binary Coded Decimal) representation because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Exact decimal representation&lt;/strong&gt;: No binary floating-point surprises like 0.1 + 0.2 != 0.3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simpler conversion&lt;/strong&gt;: Reading and printing decimal numbers is straightforward&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reasonable precision&lt;/strong&gt;: 8 BCD digits give adequate precision for an educational interpreter&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each BCD number uses 6 bytes: 1 for sign, 1 for exponent, and 4 bytes holding 8 packed decimal digits.&lt;/p&gt;
&lt;h4&gt;Cross-Compilation with SDCC&lt;/h4&gt;
&lt;p&gt;The &lt;a href="https://baud.rs/XOHX1N"&gt;Small Device C Compiler&lt;/a&gt; (SDCC) targets Z80 and other 8-bit processors. While it's an impressive project, there are quirks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No standard library functions that assume an OS&lt;/li&gt;
&lt;li&gt;Limited optimization compared to modern compilers&lt;/li&gt;
&lt;li&gt;Memory model constraints require careful attention to data placement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wrote a custom &lt;code&gt;crt0.s&lt;/code&gt; startup file that initializes the stack, sets up the serial port, and calls &lt;code&gt;main()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Running the Emulator&lt;/h3&gt;
&lt;p&gt;The emulator runs at roughly 3-4 MHz equivalent speed, depending on your browser and hardware. This is actually faster than the original Z80's typical 4 MHz, but the difference isn't noticeable for interactive use.&lt;/p&gt;
&lt;p&gt;To try it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visit the &lt;a href="https://tinycomputers.io/pages/z80-emulator.html"&gt;Z80 Emulator page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Select a ROM from the dropdown (try Fortran 77)&lt;/li&gt;
&lt;li&gt;Click "Load ROM"&lt;/li&gt;
&lt;li&gt;Click on the terminal to focus it&lt;/li&gt;
&lt;li&gt;Start typing!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For Fortran, try entering a simple program:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;PROGRAM&lt;/span&gt; &lt;span class="n"&gt;HELLO&lt;/span&gt;
&lt;span class="n"&gt;WRITE&lt;/span&gt;&lt;span class="cm"&gt;(*,*)&lt;/span&gt; &lt;span class="s"&gt;'HELLO, WORLD!'&lt;/span&gt;
&lt;span class="kr"&gt;END&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then type &lt;code&gt;RUN&lt;/code&gt; to execute it.&lt;/p&gt;
&lt;h3&gt;What's Next&lt;/h3&gt;
&lt;p&gt;There's always more to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More ROM support&lt;/strong&gt;: Expanding to additional retro languages like LISP, Logo, or Pilot&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugger integration&lt;/strong&gt;: Showing registers, memory, and allowing single-stepping&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save/restore state&lt;/strong&gt;: Persisting the emulator state to browser storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile support&lt;/strong&gt;: Touch-friendly keyboard for tablets and phones&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Source Code and Links&lt;/h3&gt;
&lt;p&gt;Everything is open source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://baud.rs/3QBPQL"&gt;Emulator source (Rust/WASM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://baud.rs/Vbb4MU"&gt;Fortran interpreter source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://baud.rs/dqAGfn"&gt;RetroShield hardware designs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://baud.rs/pX8HqT"&gt;RetroLang compiler&lt;/a&gt; - my custom language for Z80&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The RetroShield hardware is available from &lt;a href="https://baud.rs/7HAxS8"&gt;8bitforce on Tindie&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Acknowledgments&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://baud.rs/zSCNiC"&gt;Erturk Kocalar&lt;/a&gt;&lt;/strong&gt; - Creator of the RetroShield platform&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://baud.rs/a1YDGs"&gt;Andre Weissflog&lt;/a&gt;&lt;/strong&gt; - Author of the rz80 Z80 emulator core&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://baud.rs/NgAY2i"&gt;Grant Searle&lt;/a&gt;&lt;/strong&gt; - Z80 BASIC and reference designs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://baud.rs/XhhL5j"&gt;John Hardy&lt;/a&gt;&lt;/strong&gt; - Author of Firth Forth, MINT, and Monty&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's something magical about running 49-year-old CPU architectures in a modern web browser. The Z80 powered countless home computers, embedded systems, and arcade games. With this emulator, that legacy is just a click away.&lt;/p&gt;</description><category>emulator</category><category>forth</category><category>fortran</category><category>retro computing</category><category>retroshield</category><category>rust</category><category>wasm</category><category>webassembly</category><category>z80</category><guid>https://tinycomputers.io/posts/browser-based-z80-emulator-retroshield.html</guid><pubDate>Tue, 09 Dec 2025 03:00:00 GMT</pubDate></item><item><title>MCDRAG: Legacy Ballistics from 1974 BASIC to Modern Web</title><link>https://tinycomputers.io/posts/mcdrag-legacy-ballistics-from-1974-basic-to-modern-web.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/mcdrag-legacy-ballistics-from-1974-basic-to-modern-web_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;19 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;MCDRAG: When 1974 BASIC Meets Modern WebAssembly&lt;/h2&gt;
&lt;p&gt;Back in December 1974, R.L. McCoy developed MCDRAG—an algorithm for estimating drag coefficients of axisymmetric projectiles. Originally written in BASIC and designed to run on mainframes and early microcomputers, this pioneering work provided engineers with a way to quickly estimate aerodynamic properties without expensive wind tunnel testing. Today, I'm bringing this piece of ballistics history to your browser through a Rust implementation compiled to WebAssembly.&lt;/p&gt;
&lt;h3&gt;The Original: Computing Ballistics When Memory Was Measured in Kilobytes&lt;/h3&gt;
&lt;p&gt;The original MCDRAG program is a fascinating artifact of 1970s scientific computing. Written in structured BASIC with line numbers, it implements sophisticated aerodynamic calculations using only basic mathematical operations available on computers of that era. The program calculates drag coefficients across Mach numbers from 0.5 to 5.0, breaking down the total drag into components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CD0&lt;/strong&gt;: Total drag coefficient&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CDH&lt;/strong&gt;: Head drag coefficient  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CDSF&lt;/strong&gt;: Skin friction drag coefficient&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CDBND&lt;/strong&gt;: Rotating band drag coefficient&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CDBT&lt;/strong&gt;: Boattail drag coefficient&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CDB&lt;/strong&gt;: Base drag coefficient&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PB/PINF&lt;/strong&gt;: Base pressure ratio&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What's remarkable is how McCoy managed to encode complex aerodynamic relationships—including transonic effects, boundary layer transitions, and base pressure corrections—in just 260 lines of BASIC code. The program even includes diagnostic warnings for problematic geometries, alerting users when their projectile design might produce unreliable results.&lt;/p&gt;
&lt;h3&gt;The Algorithm: Physics Encoded in Code&lt;/h3&gt;
&lt;p&gt;MCDRAG uses semi-empirical methods to estimate drag, combining theoretical aerodynamics with experimental correlations. The algorithm accounts for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Flow Regime Transitions&lt;/strong&gt;: Different calculation methods for subsonic, transonic, and supersonic speeds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boundary Layer Effects&lt;/strong&gt;: Three models (Laminar/Laminar, Laminar/Turbulent, Turbulent/Turbulent) &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geometric Complexity&lt;/strong&gt;: Handles nose shapes (via the RT/R parameter), boattails, meplats, and rotating bands&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reynolds Number Effects&lt;/strong&gt;: Calculates skin friction based on flow conditions and projectile scale&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The core innovation was providing reasonable drag estimates across the entire speed range relevant to ballistics—from subsonic artillery shells to hypersonic tank rounds—using a unified computational framework.&lt;/p&gt;
&lt;h3&gt;The Modern Port: Rust + WebAssembly&lt;/h3&gt;
&lt;p&gt;My Rust implementation preserves the original algorithm's mathematical fidelity while bringing modern software engineering practices:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="cp"&gt;#[derive(Debug, Clone, Copy)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;BoundaryLayer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LaminarLaminar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LaminarTurbulent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;TurbulentTurbulent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ProjectileInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;calculate_drag_coefficients&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DragCoefficients&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Implementation follows McCoy's original algorithm&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// but with type safety and modern error handling&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The Rust version offers several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Type Safety&lt;/strong&gt;: Enum types for boundary layers prevent invalid inputs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Memory Safety&lt;/strong&gt;: No buffer overflows or undefined behavior&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Native performance in browsers via WebAssembly&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modularity&lt;/strong&gt;: Clean separation between core calculations and UI&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Try It Yourself: Interactive MCDRAG Terminal&lt;/h3&gt;
&lt;p&gt;Below is a fully functional MCDRAG calculator running entirely in your browser. No server required—all calculations happen locally using WebAssembly.&lt;/p&gt;
&lt;div id="mcdrag-terminal-container" style="width: 100%; height: 600px; margin: 20px 0;"&gt;
    &lt;div id="mcdrag-loading" style="text-align: center; padding: 20px; color: #00ff00; font-family: 'Courier New', monospace;"&gt;Loading MCDRAG terminal...&lt;/div&gt;
    &lt;div id="mcdrag-terminal" style="display: none; height: 100%;"&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;style&gt;
#mcdrag-terminal-container {
    background-color: #0a0a0a;
    border: 2px solid #00ff00;
    border-radius: 5px;
    box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
    overflow: hidden;
}

#mcdrag-terminal {
    height: 100%;
    padding: 20px;
    overflow-y: auto;
    background: linear-gradient(180deg, #0a0a0a 0%, #1a1a1a 100%);
    font-family: 'Courier New', monospace;
    color: #00ff00;
}

.mcdrag-terminal-line {
    margin: 2px 0;
    white-space: pre-wrap;
    font-size: 14px;
    line-height: 1.4;
}

.mcdrag-input-line {
    display: flex;
    align-items: center;
}

.mcdrag-prompt {
    color: #00ff00;
    margin-right: 8px;
}

#mcdrag-input-field {
    background: transparent;
    border: none;
    color: #00ff00;
    font-family: 'Courier New', monospace;
    font-size: 14px;
    outline: none;
    flex: 1;
    caret-color: #00ff00;
}

.mcdrag-output {
    color: #ffffff;
}

.mcdrag-error {
    color: #ff4444;
}

.mcdrag-warning {
    color: #ffaa00;
}

.mcdrag-header {
    color: #00ffff;
    font-weight: bold;
}

.mcdrag-table-header {
    color: #00ffff;
    border-bottom: 1px solid #00ff00;
    margin-bottom: 5px;
}

.mcdrag-table-row {
    color: #ffffff;
}
&lt;/style&gt;

&lt;script type="module"&gt;
// Dynamically load the MCDRAG WASM module
(async function() {
    try {
        // Import the WASM module from the support directory
        const module = await import('/mcdrag/mcdrag.js');
        await module.default();

        // Create the terminal interface
        class McDragTerminal {
            constructor(element) {
                this.element = element;
                this.calculator = new module.McDragCalculator();
                this.history = [];
                this.historyIndex = 0;
                this.currentState = 'IDLE';
                this.projectileData = {};
                this.fields = [
                    { key: 'ref_diameter', prompt: 'ENTER PROJECTILE REFERENCE DIAMETER (MM):', type: 'float' },
                    { key: 'total_length', prompt: 'ENTER TOTAL PROJECTILE LENGTH (CALIBERS):', type: 'float' },
                    { key: 'nose_length', prompt: 'ENTER NOSE LENGTH (CALIBERS):', type: 'float' },
                    { key: 'rt_r', prompt: 'ENTER RT/R (HEADSHAPE PARAMETER):', type: 'float' },
                    { key: 'boattail_length', prompt: 'ENTER BOATTAIL LENGTH (CALIBERS):', type: 'float' },
                    { key: 'base_diameter', prompt: 'ENTER BASE DIAMETER (CALIBERS):', type: 'float' },
                    { key: 'meplat_diameter', prompt: 'ENTER MEPLAT DIAMETER (CALIBERS):', type: 'float' },
                    { key: 'band_diameter', prompt: 'ENTER ROTATING BAND DIAMETER (CALIBERS):', type: 'float' },
                    { key: 'cg_location', prompt: 'ENTER CENTER OF GRAVITY LOCATION (CALIBERS FROM NOSE):', type: 'float' },
                    { key: 'boundary_layer', prompt: 'ENTER THE BOUNDARY LAYER CODE (L/L, L/T, OR T/T):', type: 'boundary' },
                    { key: 'identification', prompt: 'ENTER PROJECTILE IDENTIFICATION:', type: 'string' }
                ];
                this.fieldIndex = 0;
            }

            init() {
                this.clear();
                this.showWelcome();
                this.createInputLine();
            }

            showWelcome() {
                this.writeLine('MCDRAG WEB TERMINAL', 'mcdrag-header');
                this.writeLine('DECEMBER 1974, R. L. MCCOY', 'mcdrag-header');
                this.writeLine('Rust/WASM Implementation 2025\n');
                this.writeLine('Type "help" for commands or "start" to begin calculation\n');
            }

            clear() {
                this.element.innerHTML = '';
            }

            writeLine(text, className = 'mcdrag-output') {
                const line = document.createElement('div');
                line.className = `mcdrag-terminal-line ${className}`;
                line.textContent = text;
                this.element.appendChild(line);
                this.scrollToBottom();
            }

            createInputLine() {
                const inputLine = document.createElement('div');
                inputLine.className = 'mcdrag-terminal-line mcdrag-input-line';

                const prompt = document.createElement('span');
                prompt.className = 'mcdrag-prompt';
                prompt.textContent = '&gt; ';

                const input = document.createElement('input');
                input.type = 'text';
                input.id = 'mcdrag-input-field';
                input.autocomplete = 'off';

                inputLine.appendChild(prompt);
                inputLine.appendChild(input);
                this.element.appendChild(inputLine);

                // Don't auto-focus to prevent page scrolling on load
                // input.focus();
                this.scrollToBottom();

                input.addEventListener('keydown', (e) =&gt; this.handleKeyPress(e));
            }

            handleKeyPress(e) {
                const input = e.target;

                if (e.key === 'Enter') {
                    const value = input.value.trim();
                    input.parentElement.remove();
                    this.writeLine(`&gt; ${value}`);

                    if (this.currentState === 'INPUT') {
                        this.processInput(value);
                    } else {
                        this.processCommand(value);
                    }

                    this.history.push(value);
                    this.historyIndex = this.history.length;
                    this.createInputLine();
                } else if (e.key === 'ArrowUp') {
                    e.preventDefault();
                    if (this.historyIndex &gt; 0) {
                        this.historyIndex--;
                        input.value = this.history[this.historyIndex];
                    }
                } else if (e.key === 'ArrowDown') {
                    e.preventDefault();
                    if (this.historyIndex &lt; this.history.length - 1) {
                        this.historyIndex++;
                        input.value = this.history[this.historyIndex];
                    } else {
                        this.historyIndex = this.history.length;
                        input.value = '';
                    }
                }
            }

            processCommand(command) {
                switch (command.toLowerCase()) {
                    case 'help':
                        this.showHelp();
                        break;
                    case 'start':
                        this.startInput();
                        break;
                    case 'clear':
                        this.clear();
                        this.showWelcome();
                        break;
                    case 'example':
                        this.loadExample();
                        break;
                    default:
                        if (command) {
                            this.writeLine(`Unknown command: ${command}. Type "help" for available commands.`, 'mcdrag-error');
                        }
                }
            }

            showHelp() {
                this.writeLine('\nAvailable Commands:', 'mcdrag-header');
                this.writeLine('  start   - Begin new projectile calculation');
                this.writeLine('  example - Load example projectile data');
                this.writeLine('  clear   - Clear the terminal');
                this.writeLine('  help    - Show this help message\n');
            }

            loadExample() {
                this.writeLine('\nLoading example: 7.62mm NATO M80 Ball', 'mcdrag-header');
                this.projectileData = {
                    ref_diameter: 7.82,
                    total_length: 3.57,
                    nose_length: 2.12,
                    rt_r: 0.5,
                    boattail_length: 0.33,
                    base_diameter: 0.88,
                    meplat_diameter: 0.08,
                    band_diameter: 1.0,
                    cg_location: 1.7,
                    boundary_layer: 'L/T',
                    identification: '7.62mm NATO M80 Ball Example'
                };
                this.calculateAndDisplay();
            }

            startInput() {
                this.currentState = 'INPUT';
                this.fieldIndex = 0;
                this.projectileData = {};
                this.writeLine('\nENTER THE MCDRAG INPUTS, ONE QUANTITY AT A TIME.\n', 'mcdrag-header');

                if (this.fields[this.fieldIndex].key === 'cg_location') {
                    this.writeLine('[NOTE: CENTER OF GRAVITY LOCATION IS OPTIONAL; IF UNKNOWN, ENTER 0]\n', 'mcdrag-warning');
                }

                if (this.fields[this.fieldIndex].key === 'boundary_layer') {
                    this.writeLine('FOR ALL LAMINAR BOUNDARY LAYER, CODE = L/L');
                    this.writeLine('FOR LAMINAR NOSE, TURBULENT AFTERBODY, CODE = L/T');
                    this.writeLine('FOR ALL TURBULENT BOUNDARY LAYER, CODE = T/T\n');
                }

                this.writeLine(this.fields[this.fieldIndex].prompt);
            }

            processInput(value) {
                const field = this.fields[this.fieldIndex];

                if (field.type === 'float') {
                    const num = parseFloat(value);
                    if (isNaN(num)) {
                        this.writeLine('Invalid number. Please try again.', 'mcdrag-error');
                        this.writeLine(field.prompt);
                        return;
                    }
                    this.projectileData[field.key] = num;
                } else if (field.type === 'boundary') {
                    const upperValue = value.toUpperCase();
                    if (!['L/L', 'L/T', 'T/T'].includes(upperValue)) {
                        this.writeLine('INCORRECT BOUNDARY LAYER CODE. PLEASE TRY AGAIN.', 'mcdrag-error');
                        this.writeLine(field.prompt);
                        return;
                    }
                    this.projectileData[field.key] = upperValue;
                } else {
                    this.projectileData[field.key] = value;
                }

                this.fieldIndex++;

                if (this.fieldIndex &lt; this.fields.length) {
                    this.writeLine('');

                    if (this.fields[this.fieldIndex].key === 'cg_location') {
                        this.writeLine('[NOTE: CENTER OF GRAVITY LOCATION IS OPTIONAL; IF UNKNOWN, ENTER 0]', 'mcdrag-warning');
                    }

                    if (this.fields[this.fieldIndex].key === 'boundary_layer') {
                        this.writeLine('FOR ALL LAMINAR BOUNDARY LAYER, CODE = L/L');
                        this.writeLine('FOR LAMINAR NOSE, TURBULENT AFTERBODY, CODE = L/T');
                        this.writeLine('FOR ALL TURBULENT BOUNDARY LAYER, CODE = T/T');
                    }

                    this.writeLine(this.fields[this.fieldIndex].prompt);
                } else {
                    this.calculateAndDisplay();
                }
            }

            calculateAndDisplay() {
                this.currentState = 'IDLE';

                const boundaryMap = {
                    'L/L': 'LaminarLaminar',
                    'L/T': 'LaminarTurbulent',
                    'T/T': 'TurbulentTurbulent'
                };

                const inputData = {
                    ...this.projectileData,
                    boundary_layer: boundaryMap[this.projectileData.boundary_layer]
                };

                try {
                    this.calculator.set_input(JSON.stringify(inputData));
                    const resultJson = this.calculator.calculate();
                    const result = JSON.parse(resultJson);

                    this.displayResults(result);
                } catch (error) {
                    this.writeLine(`\nError: ${error}`, 'mcdrag-error');
                }
            }

            displayResults(result) {
                this.writeLine('\n=================================================================', 'mcdrag-header');
                this.writeLine('MCDRAG RESULTS', 'mcdrag-header');
                this.writeLine(`PROJECTILE: ${result.input_summary.identification}`, 'mcdrag-header');
                this.writeLine('=================================================================\n', 'mcdrag-header');

                // Results table
                this.writeLine('   M      CD0      CDH     CDSF    CDBND     CDBT     CDB    PB/PINF', 'mcdrag-table-header');

                result.coefficients.forEach(coeff =&gt; {
                    const row = `${coeff.mach.toFixed(3).padStart(6)} ${coeff.cd0.toFixed(3).padStart(7)} ` +
                              `${coeff.cdh.toFixed(3).padStart(7)} ${coeff.cdsf.toFixed(3).padStart(7)} ` +
                              `${coeff.cdbnd.toFixed(3).padStart(7)} ${coeff.cdbt.toFixed(3).padStart(7)} ` +
                              `${coeff.cdb.toFixed(3).padStart(7)} ${coeff.pb_pinf.toFixed(3).padStart(7)}`;
                    this.writeLine(row, 'mcdrag-table-row');
                });

                // Diagnostics
                if (result.diagnostics.length &gt; 0) {
                    this.writeLine('\n');
                    result.diagnostics.forEach(diag =&gt; {
                        this.writeLine(diag, 'mcdrag-warning');
                    });
                }

                this.writeLine('\n');
                this.writeLine('Type "start" for another calculation or "example" to load sample data.');
            }

            scrollToBottom() {
                this.element.scrollTop = this.element.scrollHeight;
            }
        }

        // Initialize the terminal
        const terminalElement = document.getElementById('mcdrag-terminal');
        const loadingElement = document.getElementById('mcdrag-loading');

        loadingElement.style.display = 'none';
        terminalElement.style.display = 'block';

        const terminal = new McDragTerminal(terminalElement);
        terminal.init();

    } catch (error) {
        console.error('Failed to load MCDRAG terminal:', error);
        document.getElementById('mcdrag-loading').innerHTML = 
            'Failed to load MCDRAG terminal. Please ensure JavaScript is enabled and try refreshing the page.';
    }
})();
&lt;/script&gt;

&lt;h3&gt;Using the Terminal&lt;/h3&gt;
&lt;p&gt;The terminal above provides a faithful recreation of the original MCDRAG experience with modern conveniences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;start&lt;/strong&gt;: Begin entering projectile parameters&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;example&lt;/strong&gt;: Load a pre-configured 7.62mm NATO M80 Ball example&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;clear&lt;/strong&gt;: Clear the terminal display&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;help&lt;/strong&gt;: Show available commands&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The calculator will prompt you for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reference diameter (in millimeters)&lt;/li&gt;
&lt;li&gt;Total length (in calibers - multiples of diameter)&lt;/li&gt;
&lt;li&gt;Nose length (in calibers)&lt;/li&gt;
&lt;li&gt;RT/R headshape parameter (ratio of tangent radius to actual radius)&lt;/li&gt;
&lt;li&gt;Boattail length (in calibers)&lt;/li&gt;
&lt;li&gt;Base diameter (in calibers)&lt;/li&gt;
&lt;li&gt;Meplat diameter (in calibers)&lt;/li&gt;
&lt;li&gt;Rotating band diameter (in calibers)&lt;/li&gt;
&lt;li&gt;Center of gravity location (optional, in calibers from nose)&lt;/li&gt;
&lt;li&gt;Boundary layer code (L/L, L/T, or T/T)&lt;/li&gt;
&lt;li&gt;Projectile identification name&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Historical Context: Why MCDRAG Matters&lt;/h3&gt;
&lt;p&gt;MCDRAG represents a pivotal moment in computational ballistics. Before its development, engineers relied on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Expensive wind tunnel testing for each design iteration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Simplified point-mass models that ignored aerodynamic details&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Interpolation from limited experimental data tables&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;McCoy's work democratized aerodynamic analysis, allowing engineers with access to even modest computing resources to explore design spaces rapidly. The algorithm's influence extends beyond its direct use—it established patterns for semi-empirical modeling that influenced subsequent ballistics software development.&lt;/p&gt;
&lt;h3&gt;Technical Deep Dive: The Implementation&lt;/h3&gt;
&lt;p&gt;The Rust implementation leverages several modern programming techniques while maintaining algorithmic fidelity:&lt;/p&gt;
&lt;h4&gt;Type Safety and Domain Modeling&lt;/h4&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="cp"&gt;#[derive(Debug, Serialize, Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ProjectileInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ref_diameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// D1 - Reference diameter (mm)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// L1 - Total length (calibers)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nose_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// L2 - Nose length (calibers)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rt_r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="c1"&gt;// R1 - RT/R headshape parameter&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;boattail_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// L3 - Boattail length (calibers)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;base_diameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// D2 - Base diameter (calibers)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meplat_diameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// D3 - Meplat diameter (calibers)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;band_diameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;// D4 - Rotating band diameter (calibers)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cg_location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// X1 - Center of gravity location&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;boundary_layer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;BoundaryLayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;identification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;WebAssembly Integration&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;wasm-bindgen&lt;/code&gt; crate provides seamless JavaScript interop:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="cp"&gt;#[wasm_bindgen]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;McDragCalculator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[wasm_bindgen(constructor)]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;McDragCalculator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;McDragCalculator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;current_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[wasm_bindgen]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JsValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Perform calculations and return JSON results&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;Performance Optimizations&lt;/h4&gt;
&lt;p&gt;While maintaining mathematical accuracy, the Rust version includes several optimizations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Pre-computed constants replace repeated calculations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Efficient memory layout reduces cache misses&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SIMD-friendly data structures (when compiled for native targets)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Applications and Extensions&lt;/h3&gt;
&lt;p&gt;Beyond its historical interest, MCDRAG remains useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Educational purposes&lt;/strong&gt;: Understanding fundamental aerodynamic concepts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initial design estimates&lt;/strong&gt;: Quick sanity checks before detailed CFD analysis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Embedded systems&lt;/strong&gt;: The algorithm's simplicity suits resource-constrained environments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Machine learning features&lt;/strong&gt;: MCDRAG outputs can serve as engineered features for ML models&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Open Source and Future Development&lt;/h3&gt;
&lt;p&gt;The complete source code for both the Rust library and web interface is available on &lt;a href="https://baud.rs/OPon7d"&gt;GitHub&lt;/a&gt;. The project is structured to support multiple use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Standalone CLI&lt;/strong&gt;: Native binary for command-line use&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Library&lt;/strong&gt;: Rust crate for integration into larger projects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebAssembly module&lt;/strong&gt;: Browser-ready calculations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FFI bindings&lt;/strong&gt;: C-compatible interface for other languages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Future enhancements under consideration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GPU acceleration for batch calculations&lt;/li&gt;
&lt;li&gt;Integration with modern CFD validation data&lt;/li&gt;
&lt;li&gt;Extended parameter ranges for hypersonic applications&lt;/li&gt;
&lt;li&gt;Machine learning augmentation for uncertainty quantification&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion: Bridging Eras&lt;/h3&gt;
&lt;p&gt;MCDRAG exemplifies how good engineering transcends its original context. What began as a BASIC program for 1970s mainframes now runs in your browser at speeds McCoy could hardly have imagined. Yet the core algorithm—the physics and mathematics—remains unchanged, a testament to the fundamental soundness of the approach.&lt;/p&gt;
&lt;p&gt;This project demonstrates that preserving and modernizing legacy scientific software isn't just about nostalgia. These programs encode decades of domain expertise and validated methodologies. By bringing them forward with modern tools and platforms, we make this knowledge accessible to new generations of engineers and researchers.&lt;/p&gt;
&lt;p&gt;Whether you're a ballistics engineer needing quick estimates, a student learning about aerodynamics, or a programmer interested in scientific computing history, I hope this implementation of MCDRAG proves both useful and inspiring. The terminal above isn't just a calculator—it's a bridge between computing eras, showing how far we've come while honoring where we started.&lt;/p&gt;
&lt;h3&gt;References and Further Reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;McCoy, R.L. (1974). "MCDRAG - A Computer Program for Estimating the Drag Coefficients of Projectiles." Technical Report, U.S. Army Ballistic Research Laboratory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;McCoy, R.L. (1999). "Modern Exterior Ballistics: The Launch and Flight Dynamics of Symmetric Projectiles." Schiffer Military History.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Carlucci, D.E., &amp;amp; Jacobson, S.S. (2018). "Ballistics: Theory and Design of Guns and Ammunition" (3rd ed.). CRC Press.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The MCDRAG algorithm is in the public domain. The Rust implementation and web interface are released under the BSD 3-Clause License.&lt;/em&gt;&lt;/p&gt;</description><category>ballistics</category><category>basic</category><category>drag-coefficient</category><category>projectile-design</category><category>retro computing</category><category>rust</category><category>wasm</category><guid>https://tinycomputers.io/posts/mcdrag-legacy-ballistics-from-1974-basic-to-modern-web.html</guid><pubDate>Sun, 24 Aug 2025 23:00:00 GMT</pubDate></item></channel></rss>