# Apple-I Emulator ## About This App A simulation app for running the original 1976 Apple-I computer with cycle-accurate 6502 CPU and Woz Monitor ## Specification # Apple-I Emulator (CPU Execution Fix) Build a faithful Apple-I computer emulator as an HTML5 Canvas web application that runs a cycle-accurate 6502 CPU with the original Woz Monitor ROM and character ROM, rendered at 40x24 text resolution with a vintage terminal aesthetic and a fully interactive on-screen keyboard modeled after the original Apple-I keyboard. ## App Type: HTML5 Canvas App This must be an HTML5 Canvas application — no React, no frameworks. Pure JavaScript with Canvas rendering for the terminal display, plus HTML/CSS for the on-screen keyboard overlay. ## CRITICAL: CPU Execution Loop Must Actually Run **This is the highest priority requirement.** The previous version had a bug where the CPU never actually executed instructions. The following must be verified and correct: ### CPU Boot & Run Checklist 1. **ROM must be loaded into memory BEFORE reading the reset vector.** The Woz Monitor ROM binary must be decoded from base64 and written into memory at $FF00-$FFFF *first*, then the reset vector is read from $FFFC-$FFFD to set the initial PC. If the ROM isn't loaded before reading the vector, PC will be $0000 and the CPU will execute garbage. 2. **The main execution loop must actually be started.** After initialization, `requestAnimationFrame` must be called to kick off the run loop, and inside that callback, the CPU step function must be called repeatedly (approximately 17,050 cycles per frame at 60fps to approximate 1.023 MHz). A common bug is defining the loop function but never calling it, or calling it once without re-scheduling. **Verify that `requestAnimationFrame(runLoop)` is called both initially AND at the end of each `runLoop` invocation.** 3. **The CPU `step()` function must correctly fetch, decode, and execute opcodes.** The opcode fetch must read from `memory[PC]`, increment PC, then dispatch to the correct instruction handler. A common bug is having the opcode table be empty, undefined, or having instruction handlers that don't actually modify CPU state. **Every opcode handler must update PC to point past its operand bytes** (e.g., immediate mode instructions must increment PC by 2 total, absolute mode by 3, etc.). 4. **The CPU must not be stuck in a halt/error state.** If an unrecognized opcode is encountered, the CPU should either treat it as a NOP or skip it — it must NOT stop the entire emulation loop. Add a `running` flag that defaults to `true` and is only set to `false` by explicit user action (like a "Pause" button), never by encountering an unknown opcode. 5. **Memory-mapped I/O must not block execution.** Reading $D011 (keyboard status) must always return a value immediately (bit 7 set if a key is pending, bit 7 clear otherwise). Reading $D013 (display status) must always return $00 (display ready / not busy). The Woz Monitor polls these in a loop — if they return wrong values, the CPU will spin forever or behave incorrectly. **Specifically, $D013 must have bit 7 CLEAR (value $00 or any value with bit 7 = 0) to indicate the display is ready.** The Woz Monitor checks `BIT $D012` or `BIT $D013` and branches on the N flag (bit 7). If bit 7 is stuck high, the monitor will loop forever waiting for the display. 6. **The display register ($D012) write handler must work.** When the CPU writes to $D012, the low 7 bits of the written value are the ASCII character to display. This must trigger actual screen updates — placing the character glyph on the canvas at the current cursor position and advancing the cursor. If $D012 writes are silently ignored, the Woz Monitor's `\` prompt will never appear and it will look like the CPU isn't running. 7. **The reset vector in the Woz Monitor ROM points to $FF00.** After loading the ROM, verify that `memory[0xFFFC]` is `0x00` and `memory[0xFFFD]` is `0xFF`. If these bytes are zero or wrong, the CPU will jump to the wrong address. Log these values to console during boot for debugging. 8. **Add console.log debugging breadcrumbs during boot:** Log the following to the browser console: - `"ROM loaded, first byte: " + memory[0xFF00].toString(16)` — should be `D8` (CLD instruction) - `"Reset vector: $" + (memory[0xFFFD] * 256 + memory[0xFFFC]).toString(16)` — should be `$FF00` or `$ff00` - `"CPU starting at PC=$" + PC.toString(16)` - After first 100 instructions: `"CPU alive, PC=$" + PC.toString(16) + " executed 100 instructions"` - This allows immediate verification in the browser console that the CPU is actually running. 9. **Do not use `setInterval` for the CPU loop.** Use `requestAnimationFrame` exclusively. Inside each frame, run a `while` loop that executes instructions and accumulates cycle counts until the target cycles-per-frame is reached. This ensures smooth, synchronized execution. 10. **Ensure the opcode table is fully populated.** The Woz Monitor uses at minimum these instructions: `CLD, CLI, LDA, STA, LDX, LDY, STX, STY, JSR, RTS, JMP, BNE, BEQ, BMI, BPL, BCS, BCC, CMP, CPX, CPY, INX, INY, DEX, DEY, TAX, TAY, TXA, TYA, PHA, PLA, AND, ORA, EOR, ADC, SBC, ASL, LSR, ROL, ROR, BIT, SEC, CLC, SED, NOP, BRK, INC, DEC, TSX, TXS`. All of these MUST be implemented or the monitor will crash/hang. ## Core Emulation Engine ### 6502 CPU (Cycle-Accurate) - Implement the **full NMOS 6502 instruction set** based on the provided opcode table - **Emulate the ROR bug**: On early NMOS 6502 chips (pre-1976), the ROR instruction exists but behaves incorrectly — it acts like an ASL but also rotates the carry in from the high bit side. Specifically, the ROR instruction should function but with the known buggy behavior where the carry flag is not properly shifted into bit 7 (it effectively does an ASL-like operation with carry involvement being wrong). Implement this as: the result is shifted right, BUT bit 7 gets the value of the N flag (bit 7 of the original value) instead of the carry flag, and the carry gets bit 0 correctly. This matches the known silicon bug. - Support all addressing modes: Immediate, Zero Page, Zero Page X/Y, Absolute, Absolute X/Y, Indirect X, Indirect Y, Relative, Implied, Accumulator, Indirect (JMP only) - Proper flag handling for N, Z, C, I, D, V flags per the instruction table - Branch cycle penalties: +1 for taken branch, +2 if page boundary crossed - Page boundary crossing penalties for indexed addressing modes where noted with "+" in the cycle table - Stack operations at $0100-$01FF - Reset vector read from $FFFC-$FFFD - IRQ/NMI vector support ($FFFE-$FFFF, $FFFA-$FFFB) ### Memory Map - **$0000-$00FF**: Zero Page RAM - **$0100-$01FF**: Stack RAM - **$0200-$0FFF**: General RAM - **$1000-$1FFF**: Additional RAM (programs can be loaded here) - **$D010**: Keyboard data register (PIA 6820 - read key with bit 7 set). When read, return the last key pressed with bit 7 set, AND clear the key-available flag in $D011. - **$D011**: Keyboard control register (bit 7 = key available flag, cleared on read of $D010). **Important:** This must return $00 when no key is pending, and $80 when a key is available. The Woz Monitor uses `BIT $D011; BPL` to wait for a key — BPL branches when N flag is clear (bit 7 of tested value is 0), so it loops while no key is available. - **$D012**: Display data register (write character to screen). **On write**: take the value, mask to 7 bits (value & 0x7F), and display the character on screen. This is how the Woz Monitor outputs text. - **$D013**: Display control register. **On read**: return a value with bit 7 CLEAR (e.g., $00) to indicate display is ready/not busy. The Woz Monitor checks this before writing to $D012. **If this returns $80 or $FF (bit 7 high), the monitor will hang in an infinite loop waiting for the display.** - **$FF00-$FFFF**: Woz Monitor ROM — load from the provided base64-encoded ROM binary ### Character ROM - Decode the provided base64 character ROM data - Each character is **5 pixels wide × 8 pixels tall** (5x8 font) - Characters are stored sequentially, starting from character code $00 - **Do NOT reverse/mirror the character pixel data** — render bits left-to-right as MSB-to-LSB within each byte (or however the original ROM encodes them, but explicitly: no horizontal flipping) - The ROM covers ASCII range needed by the Apple-I (uppercase letters, numbers, symbols, and control character placeholders) - Each character row is stored as one byte; the top 5 bits (or relevant bits) define the pixel columns ### Display System - **40 columns × 24 rows** text display - Each character cell renders using the 5×8 pixel character ROM - Total canvas resolution for the display area: **200×192 pixels** (scaled up for visibility) - Implement cursor: a blinking block or @ symbol at the current cursor position - Scrolling: when text reaches the bottom row and a newline occurs, scroll all rows up by one and clear the bottom row - Character display register at $D012: when the CPU writes a byte here, display the corresponding character and advance the cursor - Handle CR ($0D / carriage return) by moving cursor to the beginning of the next line - Backspace (rubout, $DF or specific code) behavior: move cursor back, optionally erase - **The display must render on every animation frame**, not just when dirty. The initial `\` prompt from the Woz Monitor must appear within the first second of page load. If nothing appears on screen after load, the CPU is not running or the display write handler is broken. ## Visual Design & Styling ### Terminal Display - **Green phosphor CRT look**: bright green (#33FF33 or similar) characters on a deep black (#0A0A0A) background - Apply a subtle **CRT curvature effect** using CSS border-radius or canvas post-processing - Add **scanline overlay** — faint horizontal lines every 2px to simulate CRT raster - Subtle **glow/bloom effect** around bright characters using CSS text-shadow or canvas shadow - The display canvas should be prominently centered at the top of the page - Scale the 200×192 pixel display up to approximately **640×460 pixels** or similar for comfortable viewing, using nearest-neighbor scaling (CSS `image-rendering: pixelated`) - Add a dark bezel/border around the display to simulate a monitor housing — rounded corners, slight gradient, dark gray (#222) to black ### Overall Page Layout - **Dark background**: very dark gray or black (#111111) page background - **Retro computer aesthetic**: think 1976 homebrew computing - Title: "Apple-I Emulator" in a retro monospace font at the very top, subtle green or amber color - Below the CRT display: the on-screen keyboard - Below the keyboard: status bar showing CPU state (optional but nice) - Warm wood-tone accent border or subtle texture behind the keyboard to reference the original Apple-I's wooden case (as seen in the keyboard photo) ### On-Screen Keyboard Model the keyboard layout precisely from the provided photograph of the original Apple-I keyboard: **Row 1 (Top — Number Row):** `ESC`, `1 !`, `2 "`, `3 #`, `4 $`, `5 %`, `6 &`, `7 '`, `8 (`, `9 )`, `0`, `* :`, `- =`, `BREAK` **Row 2 (QWERTY Row):** (small left offset), `Q`, `W`, `E`, `R`, `T`, `Y`, `U`, `I`, `O`, `P`, `@`, `LINE FEED`, `RETURN`, `CLEAR` **Row 3 (Home Row):** `CTRL`, `A`, `S`, `D`, `F`, `G`, `H`, `J`, `K`, `L`, `+ ;`, `RUB OUT`, `REPT`, `HERE IS` **Row 4 (Bottom Alpha Row):** `SHIFT`, `Z`, `X`, `C`, `V`, `B`, `N`, `M`, `, <`, `. >`, `/ ?`, `SHIFT` **Row 5 (Space Bar Row):** `SHIFT`, `[SPACEBAR — wide]`, `CLEAR` **Keyboard Styling:** - Keys should be rendered as **3D-looking rounded rectangles** with a beige/gray color (#B0A890 or similar olive-gray) matching the original keycaps - Each key has a **main label** (large, centered) and optionally a **shifted label** (smaller, top-left or top) and a **control label** (even smaller, top) - Key press animation: slight depression (translateY + darker shade) on click/touch - Dark housing around the keyboard (#333) to simulate the keyboard case - Warm wooden surface background behind/below the keyboard area (#8B6914 with subtle grain texture via CSS gradient) - Keys should be approximately 44-50px wide, with special keys (SHIFT, RETURN, SPACEBAR, etc.) proportionally wider - Subtle key gap of 3-4px between keys - Font on keys: clean sans-serif, white or light gray text ### Keyboard Functionality - Clicking/tapping a key on the on-screen keyboard sends the corresponding ASCII code to the emulated PIA at $D010 - SHIFT modifier: toggles shifted state — when active, number keys produce symbols, and the shifted label characters are sent - CTRL modifier: toggles control state — sends control codes (character code & 0x1F) - RETURN key sends CR ($8D — note Apple-I uses bit 7 high for key codes, so $0D | $80 = $8D) - All key codes sent to the PIA should have **bit 7 set** (Apple-I convention: ASCII | 0x80) - The keyboard strobe ($D011 bit 7) should be set when a key is pressed and cleared when $D010 is read - Also support **physical keyboard input**: map PC keyboard keys to Apple-I keys, intercept keydown events - Visual feedback: highlight the on-screen key when the corresponding physical key is pressed ## Functional Requirements ### Boot Sequence - On page load, initialize all RAM to $00, load the Woz Monitor ROM at $FF00-$FFFF - **Verify ROM loaded correctly by logging memory[0xFF00] (should be 0xD8) and the reset vector** - Read reset vector from $FFFC-$FFFD to get the starting PC address (should be $FF00) - Set CPU state: A=0, X=0, Y=0, SP=$FD, P=$04 (I flag set), PC=reset vector - **Immediately begin the requestAnimationFrame execution loop** - Begin executing the Woz Monitor — it should display the `\` prompt character and await keyboard input within 1-2 seconds of page load - The Woz Monitor supports: examining memory (e.g., `FF00`), modifying memory (e.g., `0300: A9 01`), running programs (e.g., `0300R`), and range display (e.g., `FF00.FFFF`) ### Execution Timing - Target approximately **1.023 MHz** clock speed (the Apple-I's actual clock) - Use `requestAnimationFrame` with cycle counting to execute the appropriate number of cycles per frame (~17,050 ## About Berrry Berrry transforms your social media content into interactive web applications. Share a Twitter/X post or Reddit comment, and our AI creates a custom web app hosted at your own subdomain. **Visit**: https://berrry.app Transform your social media ideas into real web applications today.