# Apple-I Emulator ## About This App A fully-functional Apple-I computer emulator with accurate 6502 CPU emulation, Woz Monitor, and vintage green-screen terminal experience ## Specification # Apple-I Emulator (Vanilla HTML/CSS/JS Edition) with Accurate PC Consumed Timing Create a faithful, fully-functional Apple-I computer emulator as a single-page web application using **only vanilla HTML, CSS, and JavaScript** — no frameworks, no React, no build tools. This is a love letter to the original 1976 Apple-I — complete with a working MOS 6502 CPU, Woz Monitor ROM support, BASIC ROM support, character ROM rendering, and a gorgeous on-screen keyboard replicating the original Apple-I layout. ## App Type **Plain HTML/CSS/JS Web App** — All logic is implemented in vanilla JavaScript. The terminal display is rendered on an **HTML5 Canvas** element for pixel-perfect character rendering and performance. State is managed via plain JS objects. The DOM is manipulated directly. No React, no frameworks, no bundlers. ## Overview The user uploads ROM files (Woz Monitor ROM, optional BASIC ROM, optional Character ROM) and the emulator boots into the Woz Monitor at `$FF00`, just like the real Apple-I. The screen displays a 24×40 character green-phosphor terminal, and the user interacts via an on-screen replica keyboard or their physical keyboard. The blinking cursor is rendered as an **`@` character** that blinks on and off at the current input position, faithfully replicating the real Apple-I behavior. --- ## Critical Modification: Accurate PC Consumed Timing Table The CPU emulation **MUST** use the following `_6502PCConsumed` lookup table to determine how many bytes each opcode consumes (i.e., the instruction length in bytes, which determines how far PC advances after fetching and decoding). This replaces any naive "all instructions are 1 byte" assumption and provides accurate PC advancement per opcode: ```javascript const _6502PCConsumed = [ 2, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 3, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 3, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 0, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 3, 3, 3, 3, 3 ]; ``` ### How this table is used in the CPU execution: - **A value of 0** means the opcode is an **illegal/undefined opcode** — the CPU should treat this as a NOP or halt/trap (implementation choice: either skip it as a 1-byte NOP or halt with an "illegal opcode" error displayed in the status bar). - **A value of 1** means the instruction is **1 byte** (implied/accumulator addressing — e.g., `NOP`, `TAX`, `RTS`, `PHA`, `RTI`, etc.). PC advances by 1 *only if the instruction doesn't modify PC itself* (like `RTS`, `RTI`, `JMP` implied via stack — for these, PC is set by the instruction logic, not by the table). - **A value of 2** means the instruction is **2 bytes** (opcode + 1 operand byte — zero page, immediate, relative branch addressing). PC advances by 2. - **A value of 3** means the instruction is **3 bytes** (opcode + 2 operand bytes — absolute, absolute indexed, indirect addressing). PC advances by 3. ### Integration into CPU step logic: When executing an instruction: 1. Fetch the opcode at current PC. 2. Look up `_6502PCConsumed[opcode]` to know the total instruction length. 3. Fetch any additional operand bytes (1 or 2) based on this length. 4. Execute the instruction logic. 5. **For instructions that do NOT modify PC directly** (i.e., not JMP, JSR, RTS, RTI, branches taken), advance PC by `_6502PCConsumed[opcode]`. 6. **For branches**: if the branch is taken, PC is set to the branch target. If not taken, PC advances by 2 (the value in the table for branch opcodes). 7. **For JMP, JSR, RTS, RTI**: PC is set by the instruction's own logic (JSR pushes return address then sets PC; JMP sets PC directly; RTS/RTI pull PC from stack). The table value is still useful for fetching operand bytes before execution. 8. **For illegal opcodes (value 0)**: Either treat as a 1-byte NOP and advance PC by 1, or halt the CPU and display an error. The recommended approach is to log a warning and advance PC by 1 to avoid infinite loops. This is **critical for correct emulation** — without proper PC advancement, the Woz Monitor and BASIC will not execute correctly. --- ## UI Elements ### Overall Layout & Theme - **Dark background** (#111 or pure black) to simulate looking at an old CRT monitor. - **Green phosphor text** (#33FF33 or similar) on the terminal display, with a subtle CRT glow/scanline effect. - Retro monospace font for any UI text. The terminal itself is rendered on Canvas using a built-in bitmap font or Character ROM data. - The whole page should feel like you're sitting in front of a 1976 computer in a dim garage. - All layout done with plain CSS (flexbox/grid). No CSS frameworks. ### Header Bar - Minimal top bar: "🍎 Apple-I Emulator" in a retro font, left-aligned. - Right side: small status indicators — CPU status (Running / Halted), clock speed display, and a Reset button styled as a physical toggle switch. - Implemented as a simple `
` element with flexbox layout. ### ROM Upload Panel (Startup / Sidebar) A collapsible left sidebar or modal panel for ROM management, built with plain HTML `` elements and CSS transitions for collapse/expand: - **Woz Monitor ROM** — File upload button accepting any file extension. Label: "Woz Monitor ROM (loads at $FF00–$FFFF)". Required to boot. - **BASIC ROM** — File upload button accepting any file extension. Label: "Integer BASIC ROM (loads at $E000–$EFFF)". Optional. - **Character ROM** — File upload button accepting any file extension. Label: "Character ROM (Signetics 2513 format)". Optional — if not provided, use a built-in fallback character set. - Each upload slot shows filename, file size, and a green checkmark when loaded. - A large "BOOT" button that becomes active once at least the Woz Monitor ROM is uploaded. Clicking it resets the CPU and starts execution at $FF00. - A "Load Program" option to upload a raw binary to a user-specified address (default $0280) with an `` for the hex address. ### Terminal Display (Center Stage) - **24 lines × 40 characters** — exactly matching the Apple-I video hardware. - Rendered on an **HTML5 Canvas** element for pixel-perfect control and performance. - If Character ROM is uploaded, render each character using the 5×7 pixel grid from the ROM data. Otherwise, use a **built-in bitmap font** approximating the Signetics 2513 character set. - Characters are uppercase only (the Apple-I had no lowercase). - Green text on black background with: - Subtle **CRT scanline overlay** (drawn as semi-transparent horizontal lines on the canvas, or via a CSS overlay element). - Slight **phosphor glow** effect (CSS `filter: blur()` or `box-shadow` on the canvas element, or a secondary blurred canvas layered underneath). - Optional subtle **screen curvature** via CSS border-radius or a slight CSS perspective transform for that CRT barrel distortion feel. - Optional **vignette** darkening at the screen edges via a radial-gradient CSS overlay. - **Blinking cursor is the `@` character** — at the current input position, render the `@` symbol that toggles visible/invisible at approximately 2Hz (roughly every 500ms). This faithfully replicates the real Apple-I which used `@` as its cursor. The blink state is tracked via a simple JS timer toggling a boolean, and the canvas redraws accordingly. - The terminal scrolls upward when the 24th line is filled, just like the real Apple-I. ### On-Screen Keyboard Positioned below the terminal display. Replicates the actual Apple-I keyboard layout exactly as specified. Built entirely with plain HTML `