diff --git a/CLAUDE.md b/CLAUDE.md index 0d8d691e..b7362282 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,6 +31,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Error Handling**: Use try/catch with explicit error messages - **Naming**: camelCase for variables and functions, PascalCase for classes - **Imports**: Group by source (internal/external) with proper separation +- **Function Existence Checks**: + - Never use `typeof x === 'function'` to check if methods exist + - Either directly call the method or add a stub implementation + - For optional components, use explicit object existence check (`if (this.component)`) + - Use TODOs to mark methods that need future implementation ## Project-Specific Knowledge @@ -47,10 +52,35 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - Local un-exported properties should be used for shared constants - Local constants should be used for temporary values +- **Class Exports**: + + - Classes that need to be tested must be explicitly exported + - Consider using named exports for all classes and functions that might be needed in tests + +- **Component Testing**: + + - Each component should have its own test file in tests/unit/ + - When adding new component functionality, add corresponding tests + - Always run tests after making changes: `npm run test:unit` + - **Pre-commit Hooks**: - The project uses lint-staged with ESLint - Watch for unused variables and ensure proper error handling + - Run linting check before committing: `npm run lint` ### Git Workflow - When creating branches with Claude, use the `claude/` prefix (e.g., `claude/fix-esm-import-error`) +- Always run linting and tests before committing changes +- Update issue notes with progress for long-running feature implementations + +### Save State Implementation + +- Save state functionality uses a component-based approach +- Each component (CPU, video, scheduler, etc.) implements saveState/loadState methods +- A central SaveState class coordinates serialization across components +- TimeTravel class provides rewind buffer functionality +- SaveStateStorage handles browser local storage integration +- Tests cover each component's ability to save and restore its state +- The load order of components is important - scheduler should be loaded before peripherals +- VIA and ACIA state is critical for proper task scheduling after loading diff --git a/docs/BemSnpFormat.md b/docs/BemSnpFormat.md new file mode 100644 index 00000000..baf51484 --- /dev/null +++ b/docs/BemSnpFormat.md @@ -0,0 +1,263 @@ +# B-Em .snp Snapshot File Format + +This document describes the B-Em snapshot file format (`.snp`) to allow compatibility with jsbeeb for testing and verification purposes. + +## File Structure Overview + +B-Em snapshot files use a modular format with each component saving its state in a separate section. The file starts with a fixed header followed by multiple sections, each with its own header and data. + +### File Header + +``` +Offset Size Description +0x00 7 Magic identifier: "BEMSNAP" +0x07 1 Version (current is '3') +``` + +### Section Format + +Each section consists of: + +For regular (uncompressed) sections: + +``` +Offset Size Description +0x00 1 Section key (ASCII character identifier) +0x01 2 Section size (little-endian) +0x03 n Section data (n bytes as specified in size) +``` + +For compressed sections (using zlib): + +``` +Offset Size Description +0x00 1 Section key with bit 7 set (key | 0x80) +0x01 4 Section size (little-endian) +0x05 n Compressed section data (n bytes as specified in size) +``` + +## Section Keys + +The following section keys are used: + +| Key | Description | Compressed | Component | +| --- | -------------------- | ---------- | -------------- | +| 'm' | Model info | No | Model | +| '6' | 6502 CPU state | No | 6502 CPU | +| 'M' | Main memory | Yes | Memory | +| 'S' | System VIA | No | System VIA | +| 'U' | User VIA | No | User VIA | +| 'V' | Video ULA | No | Video ULA | +| 'C' | CRTC state | No | CRTC | +| 'v' | Video state | No | Video | +| 's' | Sound chip (SN76489) | No | Sound chip | +| 'A' | ADC state | No | ADC | +| 'a' | System ACIA | No | System ACIA | +| 'r' | Serial ULA | No | Serial | +| 'F' | VDFS state | No | VDFS | +| '5' | Music 5000 | No | Music 5000 | +| 'p' | Paula sound | No | Paula | +| 'J' | JIM (paged RAM) | Yes | JIM memory | +| 'T' | Tube ULA | No | Tube | +| 'P' | Tube processor | Yes | Tube processor | + +## Section Data Formats + +### 6502 CPU State ('6') + +``` +Offset Size Description +0x00 1 A register +0x01 1 X register +0x02 1 Y register +0x03 1 Processor status flags (packed) +0x04 1 Stack pointer +0x05 2 Program counter (little-endian) +0x07 1 NMI status +0x08 1 Interrupt status +0x09 4 Cycle count (little-endian, 32-bit) +``` + +The processor status flags are packed as follows: + +- Bit 7: N (negative) +- Bit 6: V (overflow) +- Bit 5: Always 1 +- Bit 4: B (break) +- Bit 3: D (decimal) +- Bit 2: I (interrupt disable) +- Bit 1: Z (zero) +- Bit 0: C (carry) + +### Main Memory ('M') + +This section is zlib-compressed and contains: + +``` +Offset Size Description +0x00 1 FE30 latch (memory banking) +0x01 1 FE34 latch (memory banking) +0x02 32KB RAM contents +0x8002 16KB*16 ROM contents (16 ROM slots) +``` + +### VIA State ('S' for System VIA, 'U' for User VIA) + +Both VIAs use the same structure with the System VIA section ('S') having one additional byte for the IC32 latch: + +``` +Offset Size Description +0x00 1 Output Register A (ORA) +0x01 1 Output Register B (ORB) +0x02 1 Input Register A (IRA) +0x03 1 Input Register B (IRB) +0x04 1 Port A Read Value +0x05 1 Port A Read Value (repeated) +0x06 1 Data Direction Register A (DDRA) +0x07 1 Data Direction Register B (DDRB) +0x08 1 Shift Register (SR) +0x09 1 Auxiliary Control Register (ACR) +0x0A 1 Peripheral Control Register (PCR) +0x0B 1 Interrupt Flag Register (IFR) +0x0C 1 Interrupt Enable Register (IER) +0x0D 4 Timer 1 Latch (T1L) - 32-bit, little-endian +0x11 4 Timer 2 Latch (T2L) - 32-bit, little-endian +0x15 4 Timer 1 Counter (T1C) - 32-bit, little-endian +0x19 4 Timer 2 Counter (T2C) - 32-bit, little-endian +0x1D 1 Timer 1 Hit Flag +0x1E 1 Timer 2 Hit Flag +0x1F 1 CA1 State +0x20 1 CA2 State +``` + +For System VIA only: + +``` +0x21 1 IC32 latch (video control) +``` + +### ACIA State ('a') + +``` +Offset Size Description +0x00 1 Control Register +0x01 1 Status Register +``` + +### Video ULA State ('V') + +``` +Offset Size Description +0x00 1 Control Register +0x01 16 16 palette entries (1 byte each) +0x11 64 NuLA color palette (4 bytes per color: RGBA, 16 colors) +0x51 1 NuLA palette write flag +0x52 1 NuLA palette first byte +0x53 8 NuLA flash values (8 bytes) +0x5B 1 NuLA palette mode +0x5C 1 NuLA horizontal offset +0x5D 1 NuLA left blank +0x5E 1 NuLA disable flag +0x5F 1 NuLA attribute mode +0x60 1 NuLA attribute text +``` + +### CRTC State ('C') + +``` +Offset Size Description +0x00 18 CRTC registers (0-17) +0x12 1 Vertical Counter (VC) +0x13 1 Scan Counter (SC) +0x14 1 Horizontal Counter (HC) +0x15 2 Memory Address (MA) - 16-bit, little-endian +0x17 2 Memory Address Backup (MABack) - 16-bit, little-endian +``` + +### Video State ('v') + +``` +Offset Size Description +0x00 2 Screen X position (scrx) - 16-bit, little-endian +0x02 2 Screen Y position (scry) - 16-bit, little-endian +0x04 1 Odd Clock flag +0x05 4 Video Clocks counter (vidclocks) - 32-bit, little-endian +``` + +### Sound Chip State ('s') + +``` +Offset Size Description +0x00 16 SN Latch values (16 bytes) +0x10 16 SN Count values (16 bytes) +0x20 16 SN Status values (16 bytes) +0x30 4 SN Volume values (4 bytes) +0x34 1 SN Noise value +0x35 2 SN Shift register - 16-bit, little-endian +``` + +### ADC State ('A') + +``` +Offset Size Description +0x00 1 ADC Status +0x01 1 ADC Low Byte +0x02 1 ADC High Byte +0x03 1 ADC Latch +0x04 1 ADC Time +``` + +### Serial ULA State ('r') + +``` +Offset Size Description +0x00 1 Serial Register +``` + +## Loading B-Em Snapshots + +To load a B-Em snapshot: + +1. Verify the file header matches "BEMSNAP" with a version between '1' and '3' +2. Parse each section based on its key and size +3. For compressed sections (key & 0x80), decompress using zlib +4. Apply each component's state in the correct order: + - Model + - 6502 CPU + - Memory + - System VIA + - User VIA + - Video ULA + - CRTC + - Video state + - Sound chip + - Other peripherals + +## Compatibility Notes + +- Version '3' is the most recent and includes all components +- Earlier versions ('1' and '2') have slightly different formats and fewer components +- For jsbeeb compatibility, focus on the core components: 6502 CPU, memory, and VIAs +- The memory layout might differ between B-Em and jsbeeb, requiring translation +- Version '1' has a different loading order of sections (see `load_state_one` function in savestate.c) +- Version '2' uses a different section header format (see `load_state_two` function) + +## Using Snapshots for Testing + +B-Em snapshots can be valuable for testing jsbeeb by: + +1. Creating known-state snapshots in B-Em for specific tests +2. Loading these snapshots in jsbeeb +3. Running the same code sequence in both emulators +4. Comparing final states to verify emulation accuracy + +## Snapshot Generation for Testing + +To create useful test snapshots: + +1. Start B-Em with a specific configuration +2. Load or type in a test program +3. Run the program to a specific point +4. Save a snapshot using the "Save Snapshot" option +5. Document the exact state and expected behavior +6. Use this snapshot as a starting point for comparison testing diff --git a/docs/SaveState.md b/docs/SaveState.md new file mode 100644 index 00000000..3b27b277 --- /dev/null +++ b/docs/SaveState.md @@ -0,0 +1,296 @@ +# jsbeeb Save State Implementation Design + +This document outlines the design and implementation plan for adding save state functionality to jsbeeb. The goal is to allow users to save the entire emulator state at any point and restore it later, providing a seamless experience when resuming emulation sessions. + +## Goals + +- Create a comprehensive save state system that captures all necessary emulator state +- Support saving to and loading from browser local storage +- Implement a rewind (time travel) feature using a ring buffer of states +- Provide compatibility with other BBC Micro emulator save state formats +- Ensure the implementation is efficient in terms of storage size and performance +- **Support cross-model state restoration** (e.g., loading a BBC B state into a Master) + +## Architecture + +The save state system will be built around a central `SaveState` class that coordinates saving and loading state from all emulator components. Each component will implement methods to save and restore its state. The system will also store machine model information to ensure correct restoration across different machine configurations. + +### Core Components + +1. **SaveState Class**: Manages the overall state serialization and deserialization +2. **Component State Interface**: Standard methods for components to save/restore state +3. **Model Information**: Captures the machine configuration (BBC B, Master, etc.) and peripheral setup +4. **Serialization Module**: Handles converting state to/from storable formats +5. **Storage Interface**: Manages saving to localStorage, files, etc. +6. **Time Machine**: Implements the rewind functionality using a ring buffer +7. **Model Restoration Manager**: Handles reconfiguring the machine when loading states from different models + +## Component State Implementation + +Each component will need to implement methods to save and restore its state: + +### CPU State (6502.js) + +- Registers (a, x, y, s, pc) +- Processor flags +- Interrupt and NMI state +- Memory access state +- CPU timing information + +### Machine Model State + +- BBC Micro model type (B, B+, Master 128, etc.) +- CPU type (6502, 65C02, 65C12) +- FDC type (Intel 8271, WD 1770, etc.) +- Memory configuration (shadow RAM, etc.) +- ROM selection and mapping +- Peripheral configuration (available hardware) + +### Memory State + +- RAM contents +- ROM selection and mapping +- Shadow RAM configuration (for Master) +- Memory paging state + +### Video State (video.js) + +- CRTC registers +- ULA state and palette +- Rendering state (scanline, position) +- Display mode +- Teletext state (if applicable) + +### Sound State (soundchip.js) + +- Sound chip registers +- Tone generator state +- Music 5000 state (if present) + +### I/O State + +- VIA states (sysvia, uservia) +- ACIA state +- FDC state +- Other peripherals (ADC, serial, econet) + +### Timing State (scheduler.js) + +- Scheduler epoch +- Scheduled tasks with timing information +- Frame timing and sync state + +### Disc/Tape State + +- Disc drive state (motor, head position) +- Media state (loaded disc images) +- Tape position and state + +## Serialization Format + +The save state will be serialized in a structured format with: + +1. **Header**: Version, timestamp, metadata +2. **Model Information**: BBC Micro model and configuration details +3. **Component Blocks**: Serialized state for each component +4. **Binary Data**: Efficient storage for large arrays (RAM, etc.) + +Two serialization formats will be supported: + +- **Binary Format**: Compact representation for storage efficiency +- **JSON Format**: Human-readable format for debugging and inspection + +## Storage Implementation + +### Local Storage + +- Save/load from browser localStorage with size limitation handling +- Fallback to IndexedDB for larger states + +### File System + +- Export/import save states as files +- Standard file format (.jss - jsbeeb state) + +### State Naming and Management + +- Named save slots +- Automatic timestamping +- Optional thumbnails of the screen state + +## Time Travel (Rewind) Implementation + +### State Ring Buffer + +- Circular buffer storing recent states +- Configurable buffer size and capture frequency +- Memory-efficient delta encoding between adjacent states + +### Rewind Controls + +- UI controls for navigating through saved states +- Keyboard shortcuts for quick access +- Visual timeline representation + +## Format Compatibility + +### B-EM Format Support + +- Parser/generator for B-EM save state format +- Mapping between B-EM and jsbeeb component representations + +### Other Formats + +- Extensible design to support additional formats in the future + +## User Interface + +### Save/Load Controls + +- Buttons for quick save/load +- Menu for named save slots +- Keyboard shortcuts + +### State Management + +- List view of saved states +- Ability to rename, delete, export states +- State metadata display + +## Implementation Phases + +1. **Phase 1**: Core SaveState class and component interface +2. **Phase 2**: CPU and memory state implementation +3. **Phase 3**: Video and critical peripherals +4. **Phase 4**: Serialization and local storage +5. **Phase 5**: Remaining components +6. **Phase 6**: Rewind functionality +7. **Phase 7**: Format conversion +8. **Phase 8**: UI integration + +## Technical Considerations + +### Storage Efficiency + +- Typed arrays for binary data +- Simple compression for large blocks +- Delta encoding for rewind buffer + +### Timing Accuracy + +- Careful handling of cycle counting +- Preservation of interrupt timing +- Frame synchronization + +### Compatibility + +- Version checking for future-proofing +- Graceful handling of incompatible states + +### Debugging Support + +- Human-readable JSON format option +- State diffing tools + +## Code Organization + +``` +src/ +├── savestate.js # Core SaveState class +├── savestate/ +│ ├── formats.js # Format converters +│ ├── serializer.js # Serialization helpers +│ ├── storage.js # Storage integration +│ └── timemachine.js # Rewind functionality +``` + +## API Design (Proposed) + +```javascript +// Save state interface +class SaveState { + constructor(version = 1) { ... } + serialize() { ... } // Convert to storable format + static deserialize(data) { ... } // Restore from stored format + toJSON() { ... } // Convert to JSON for debugging + toFile() { ... } // Export to file + static fromFile(file) { ... } // Import from file + + // Model handling + getModelInfo() { ... } // Get the stored model information + addModelInfo(model) { ... } // Store model information +} + +// Component interface +class Component { + saveState(state) { ... } // Save component state + loadState(state) { ... } // Restore component state +} + +// Model management interface +class ModelManager { + static getModelFromSaveState(state) { ... } // Extract model info + static configureForModel(model) { ... } // Reconfigure emulator +} + +// User-facing API +emulator.saveState() => SaveState // Create a save state +emulator.saveStateToSlot(name) => void // Save to named slot +emulator.loadState(state) => void // Load a save state +emulator.loadStateFromSlot(name) => void // Load from named slot +emulator.getStateList() => string[] // List available states +emulator.deleteState(name) => void // Delete a state +emulator.rewind(seconds) => void // Rewind emulation +emulator.getModelInfo() => Object // Get current machine model info +``` + +## Testing Strategy + +The save state implementation is accompanied by a comprehensive testing strategy to ensure reliability and correctness: + +### Unit Testing + +- **SaveState Class**: Tests for serialization, deserialization, typed array handling, and nested object structures +- **TimeTravel Class**: Tests for buffer management, state rotation, and timing behavior +- **SaveStateStorage Class**: Tests for localStorage integration, error handling, and state management +- **Component Integration**: Tests for each component's save/load state methods + +### Integration Testing + +- Tests for complete emulator state saving and restoration +- Tests for state compatibility across different emulator configurations +- Performance testing for large state sizes + +### Format Compatibility Testing + +- Tests for importing/exporting B-EM format states +- Tests for format version handling and backwards compatibility + +### Cross-Model Testing + +- Tests for saving state on one model and loading on another +- Tests for proper model detection and reconfiguration +- Tests for handling peripheral differences between models + +## Model-Aware State Loading Process + +When loading a save state, the system will follow these steps: + +1. **Extract Model Information**: Read the model information from the save state +2. **Reconfigure Emulator**: Reconfigure the emulator to match the saved model: + - Set machine type (B, B+, Master 128, etc.) + - Adjust CPU type (6502/65C02/65C12) + - Set up memory layout (standard/shadow RAM) + - Configure appropriate peripherals (Intel/WD FDC, etc.) +3. **Load Component States**: Load component states in the correct order: + - First load scheduler state + - Then load CPU core state + - Load memory state + - Load peripheral states in dependency order +4. **Post-Load Validation**: Verify critical state was restored properly + +This approach ensures that save states can be safely loaded regardless of the current emulator configuration. + +## Conclusion + +This save state implementation will significantly enhance the usability of jsbeeb by allowing users to save their progress and resume sessions later. The model-aware design ensures that users can load states across different BBC Micro configurations without compatibility issues. The rewind feature will provide a valuable tool for debugging and exploration. The implementation will be done in phases, with careful attention to component state preservation, model compatibility, storage efficiency, and user experience, all backed by comprehensive testing. diff --git a/index.html b/index.html index 12f80cef..a8fa34bd 100644 --- a/index.html +++ b/index.html @@ -126,6 +126,24 @@