ac6502 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/components/CPU.js +1170 -0
  4. package/dist/components/CPU.js.map +1 -0
  5. package/dist/components/Cart.js +23 -0
  6. package/dist/components/Cart.js.map +1 -0
  7. package/dist/components/IO/Empty.js +19 -0
  8. package/dist/components/IO/Empty.js.map +1 -0
  9. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
  10. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
  11. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
  12. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
  13. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
  14. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
  15. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/GPIOCard.js +597 -0
  18. package/dist/components/IO/GPIOCard.js.map +1 -0
  19. package/dist/components/IO/InputBoard.js +19 -0
  20. package/dist/components/IO/InputBoard.js.map +1 -0
  21. package/dist/components/IO/LCDCard.js +19 -0
  22. package/dist/components/IO/LCDCard.js.map +1 -0
  23. package/dist/components/IO/RAMCard.js +63 -0
  24. package/dist/components/IO/RAMCard.js.map +1 -0
  25. package/dist/components/IO/RTCCard.js +483 -0
  26. package/dist/components/IO/RTCCard.js.map +1 -0
  27. package/dist/components/IO/SerialCard.js +282 -0
  28. package/dist/components/IO/SerialCard.js.map +1 -0
  29. package/dist/components/IO/SoundCard.js +620 -0
  30. package/dist/components/IO/SoundCard.js.map +1 -0
  31. package/dist/components/IO/StorageCard.js +428 -0
  32. package/dist/components/IO/StorageCard.js.map +1 -0
  33. package/dist/components/IO/VGACard.js +9 -0
  34. package/dist/components/IO/VGACard.js.map +1 -0
  35. package/dist/components/IO/VideoCard.js +623 -0
  36. package/dist/components/IO/VideoCard.js.map +1 -0
  37. package/dist/components/IO.js +3 -0
  38. package/dist/components/IO.js.map +1 -0
  39. package/dist/components/Machine.js +310 -0
  40. package/dist/components/Machine.js.map +1 -0
  41. package/dist/components/RAM.js +24 -0
  42. package/dist/components/RAM.js.map +1 -0
  43. package/dist/components/ROM.js +23 -0
  44. package/dist/components/ROM.js.map +1 -0
  45. package/dist/index.js +441 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/tests/CPU.test.js +1626 -0
  48. package/dist/tests/CPU.test.js.map +1 -0
  49. package/dist/tests/Cart.test.js +119 -0
  50. package/dist/tests/Cart.test.js.map +1 -0
  51. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
  52. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
  53. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
  54. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
  55. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
  56. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
  57. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
  58. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
  59. package/dist/tests/IO/GPIOCard.test.js +503 -0
  60. package/dist/tests/IO/GPIOCard.test.js.map +1 -0
  61. package/dist/tests/IO/RAMCard.test.js +229 -0
  62. package/dist/tests/IO/RAMCard.test.js.map +1 -0
  63. package/dist/tests/IO/RTCCard.test.js +177 -0
  64. package/dist/tests/IO/RTCCard.test.js.map +1 -0
  65. package/dist/tests/IO/SerialCard.test.js +423 -0
  66. package/dist/tests/IO/SerialCard.test.js.map +1 -0
  67. package/dist/tests/IO/SoundCard.test.js +528 -0
  68. package/dist/tests/IO/SoundCard.test.js.map +1 -0
  69. package/dist/tests/IO/StorageCard.test.js +647 -0
  70. package/dist/tests/IO/StorageCard.test.js.map +1 -0
  71. package/dist/tests/IO/VideoCard.test.js +549 -0
  72. package/dist/tests/IO/VideoCard.test.js.map +1 -0
  73. package/dist/tests/Machine.test.js +383 -0
  74. package/dist/tests/Machine.test.js.map +1 -0
  75. package/dist/tests/RAM.test.js +160 -0
  76. package/dist/tests/RAM.test.js.map +1 -0
  77. package/dist/tests/ROM.test.js +123 -0
  78. package/dist/tests/ROM.test.js.map +1 -0
  79. package/jest.config.cjs +9 -0
  80. package/package.json +43 -0
  81. package/src/components/CPU.ts +1371 -0
  82. package/src/components/Cart.ts +20 -0
  83. package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
  84. package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
  85. package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
  86. package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
  87. package/src/components/IO/GPIOCard.ts +677 -0
  88. package/src/components/IO/RAMCard.ts +68 -0
  89. package/src/components/IO/RTCCard.ts +518 -0
  90. package/src/components/IO/SerialCard.ts +335 -0
  91. package/src/components/IO/SoundCard.ts +711 -0
  92. package/src/components/IO/StorageCard.ts +473 -0
  93. package/src/components/IO/VideoCard.ts +730 -0
  94. package/src/components/IO.ts +11 -0
  95. package/src/components/Machine.ts +364 -0
  96. package/src/components/RAM.ts +23 -0
  97. package/src/components/ROM.ts +19 -0
  98. package/src/index.ts +474 -0
  99. package/src/tests/CPU.test.ts +2045 -0
  100. package/src/tests/Cart.test.ts +149 -0
  101. package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
  102. package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
  103. package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
  104. package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
  105. package/src/tests/IO/GPIOCard.test.ts +644 -0
  106. package/src/tests/IO/RAMCard.test.ts +284 -0
  107. package/src/tests/IO/RTCCard.test.ts +222 -0
  108. package/src/tests/IO/SerialCard.test.ts +530 -0
  109. package/src/tests/IO/SoundCard.test.ts +659 -0
  110. package/src/tests/IO/StorageCard.test.ts +787 -0
  111. package/src/tests/IO/VideoCard.test.ts +668 -0
  112. package/src/tests/Machine.test.ts +437 -0
  113. package/src/tests/RAM.test.ts +196 -0
  114. package/src/tests/ROM.test.ts +154 -0
  115. package/tsconfig.json +12 -0
@@ -0,0 +1,437 @@
1
+ import { Machine } from '../components/Machine'
2
+ import { RAM } from '../components/RAM'
3
+ import { ROM } from '../components/ROM'
4
+ import { Cart } from '../components/Cart'
5
+
6
+ describe('Machine', () => {
7
+ let machine: Machine
8
+
9
+ beforeEach(() => {
10
+ machine = new Machine()
11
+ })
12
+
13
+ afterEach(() => {
14
+ // Ensure the machine loop is stopped after each test
15
+ machine.end()
16
+ })
17
+
18
+ describe('Initialization', () => {
19
+ test('Constructor creates a Machine instance', () => {
20
+ expect(machine).not.toBeNull()
21
+ expect(machine).toBeInstanceOf(Machine)
22
+ })
23
+
24
+ test('Machine initializes with correct default properties', () => {
25
+ expect(machine.isAlive).toBe(false)
26
+ expect(machine.isRunning).toBe(false)
27
+ expect(machine.frequency).toBe(2000000)
28
+ expect(machine.scale).toBe(2)
29
+ expect(machine.frames).toBe(0)
30
+ })
31
+
32
+ test('Machine creates CPU, RAM, ROM, and IO cards', () => {
33
+ expect(machine.cpu).not.toBeNull()
34
+ expect(machine.ram).not.toBeNull()
35
+ expect(machine.rom).not.toBeNull()
36
+ expect(machine.io1).not.toBeNull()
37
+ expect(machine.io2).not.toBeNull()
38
+ expect(machine.io3).not.toBeNull()
39
+ expect(machine.io4).not.toBeNull()
40
+ expect(machine.io5).not.toBeNull()
41
+ expect(machine.io6).not.toBeNull()
42
+ expect(machine.io7).not.toBeNull()
43
+ expect(machine.io8).not.toBeNull()
44
+ })
45
+
46
+ test('Machine has no cart initially', () => {
47
+ expect(machine.cart).toBeUndefined()
48
+ })
49
+
50
+ test('Machine has CPU reset on creation', () => {
51
+ // CPU should be reset, which sets up initial state
52
+ expect(machine.cpu).toBeDefined()
53
+ })
54
+ })
55
+
56
+ describe('State Management', () => {
57
+ test('start() sets isRunning and isAlive to true', () => {
58
+ expect(machine.isRunning).toBe(false)
59
+ expect(machine.isAlive).toBe(false)
60
+ machine.start()
61
+ expect(machine.isRunning).toBe(true)
62
+ expect(machine.isAlive).toBe(true)
63
+ })
64
+
65
+ test('end() sets isRunning and isAlive to false', () => {
66
+ machine.start()
67
+ expect(machine.isRunning).toBe(true)
68
+ machine.end()
69
+ expect(machine.isRunning).toBe(false)
70
+ expect(machine.isAlive).toBe(false)
71
+ })
72
+
73
+ test('run() sets isRunning to true', () => {
74
+ expect(machine.isRunning).toBe(false)
75
+ machine.run()
76
+ expect(machine.isRunning).toBe(true)
77
+ })
78
+
79
+ test('stop() sets isRunning to false', () => {
80
+ machine.run()
81
+ expect(machine.isRunning).toBe(true)
82
+ machine.stop()
83
+ expect(machine.isRunning).toBe(false)
84
+ })
85
+ })
86
+
87
+ describe('Memory Access - Read Operations', () => {
88
+ test('Reading from RAM returns stored values', () => {
89
+ const address = 0x0100
90
+ machine.ram.write(address, 0xAB)
91
+ expect(machine.read(address)).toBe(0xAB)
92
+ })
93
+
94
+ test('Reading from uninitialized RAM returns 0', () => {
95
+ expect(machine.read(0x0200)).toBe(0)
96
+ })
97
+
98
+ test('Reading from IO1 address space', () => {
99
+ const ioAddress = 0x8000
100
+ machine.io1.write(0, 0x55)
101
+ expect(machine.read(ioAddress)).toBe(0x55)
102
+ })
103
+
104
+ test('Reading from IO2 address space', () => {
105
+ const ioAddress = 0x8400
106
+ machine.io2.write(0, 0x66)
107
+ expect(machine.read(ioAddress)).toBe(0x66)
108
+ })
109
+
110
+ test('Reading from IO3 (RTC) address space', () => {
111
+ const ioAddress = 0x8800
112
+ const result = machine.read(ioAddress)
113
+ expect(typeof result).toBe('number')
114
+ })
115
+
116
+ test('Reading from IO4 (Storage) address space', () => {
117
+ const ioAddress = 0x8C00
118
+ const result = machine.read(ioAddress)
119
+ expect(typeof result).toBe('number')
120
+ })
121
+
122
+ test('Reading from IO5 (Serial) address space', () => {
123
+ const ioAddress = 0x9000
124
+ const result = machine.read(ioAddress)
125
+ expect(typeof result).toBe('number')
126
+ })
127
+
128
+ test('Reading from IO6 (GPIO) address space', () => {
129
+ const ioAddress = 0x9400
130
+ const result = machine.read(ioAddress)
131
+ expect(typeof result).toBe('number')
132
+ })
133
+
134
+ test('Reading from IO7 (Sound) address space', () => {
135
+ const ioAddress = 0x9800
136
+ const result = machine.read(ioAddress)
137
+ expect(typeof result).toBe('number')
138
+ })
139
+
140
+ test('Reading from IO8 (Video) address space', () => {
141
+ const ioAddress = 0x9C00
142
+ const result = machine.read(ioAddress)
143
+ expect(typeof result).toBe('number')
144
+ })
145
+
146
+ test('Reading from invalid address returns 0', () => {
147
+ // Assuming unmapped space returns 0
148
+ expect(machine.read(0x10000)).toBe(0)
149
+ })
150
+
151
+ test('Reading from ROM address space', () => {
152
+ const romAddress = 0xA000
153
+ const result = machine.read(romAddress)
154
+ expect(typeof result).toBe('number')
155
+ })
156
+ })
157
+
158
+ describe('Memory Access - Write Operations', () => {
159
+ test('Writing to RAM stores values', () => {
160
+ const address = 0x0100
161
+ machine.write(address, 0xCD)
162
+ expect(machine.ram.read(address)).toBe(0xCD)
163
+ })
164
+
165
+ test('Writing to IO1 address space', () => {
166
+ const ioAddress = 0x8000
167
+ machine.write(ioAddress, 0x42)
168
+ expect(machine.io1.read(0)).toBe(0x42)
169
+ })
170
+
171
+ test('Writing to IO2 address space', () => {
172
+ const ioAddress = 0x8400
173
+ machine.write(ioAddress, 0x43)
174
+ expect(machine.io2.read(0)).toBe(0x43)
175
+ })
176
+
177
+ test('Writing to IO3 address space', () => {
178
+ const ioAddress = 0x8800
179
+ expect(() => machine.write(ioAddress, 0x44)).not.toThrow()
180
+ })
181
+
182
+ test('Writing to IO4 address space', () => {
183
+ const ioAddress = 0x8C00
184
+ expect(() => machine.write(ioAddress, 0x45)).not.toThrow()
185
+ })
186
+
187
+ test('Writing to IO5 address space', () => {
188
+ const ioAddress = 0x9000
189
+ expect(() => machine.write(ioAddress, 0x46)).not.toThrow()
190
+ })
191
+
192
+ test('Writing to IO6 address space', () => {
193
+ const ioAddress = 0x9400
194
+ expect(() => machine.write(ioAddress, 0x47)).not.toThrow()
195
+ })
196
+
197
+ test('Writing to IO7 address space', () => {
198
+ const ioAddress = 0x9800
199
+ expect(() => machine.write(ioAddress, 0x48)).not.toThrow()
200
+ })
201
+
202
+ test('Writing to IO8 address space', () => {
203
+ const ioAddress = 0x9C00
204
+ expect(() => machine.write(ioAddress, 0x49)).not.toThrow()
205
+ })
206
+
207
+ test('Writing to ROM address space does nothing', () => {
208
+ const romAddress = 0xA000
209
+ expect(() => machine.write(romAddress, 0xFF)).not.toThrow()
210
+ })
211
+ })
212
+
213
+ describe('Cart Operations', () => {
214
+ test('Cart is initially undefined', () => {
215
+ expect(machine.cart).toBeUndefined()
216
+ })
217
+
218
+ test('loadCart should load cart data', async () => {
219
+ // Create a mock cart file path - this will test the error handling
220
+ await machine.loadCart('/nonexistent/path.bin')
221
+ // The machine should handle the error gracefully
222
+ expect(machine.cart).toBeUndefined()
223
+ })
224
+ })
225
+
226
+ describe('ROM Operations', () => {
227
+ test('loadROM should load ROM data', async () => {
228
+ // Create a mock ROM file path - this will test the error handling
229
+ await machine.loadROM('/nonexistent/path.bin')
230
+ // The machine should handle the error gracefully
231
+ expect(machine.rom).toBeDefined()
232
+ })
233
+ })
234
+
235
+ describe('CPU Execution', () => {
236
+ test('step() executes one instruction', () => {
237
+ const initialCycles = machine.cpu.cycles
238
+ machine.step()
239
+ // Step should execute at least one cycle
240
+ expect(machine.cpu.cycles).toBeGreaterThanOrEqual(initialCycles)
241
+ })
242
+
243
+ test('tick() executes one CPU clock cycle', () => {
244
+ const initialCycles = machine.cpu.cycles
245
+ machine.tick()
246
+ // tick() increments CPU state; cycles may stay same if already counted
247
+ expect(machine.cpu.cycles).toBeGreaterThanOrEqual(initialCycles)
248
+ })
249
+
250
+ test('multiple steps execute multiple instructions', () => {
251
+ const initialCycles = machine.cpu.cycles
252
+ machine.step()
253
+ machine.step()
254
+ machine.step()
255
+ expect(machine.cpu.cycles).toBeGreaterThan(initialCycles)
256
+ })
257
+
258
+ test('multiple ticks increment cycle counter', () => {
259
+ const initialCycles = machine.cpu.cycles
260
+ for (let i = 0; i < 10; i++) {
261
+ machine.tick()
262
+ }
263
+ expect(machine.cpu.cycles).toBeGreaterThan(initialCycles)
264
+ })
265
+ })
266
+
267
+ describe('Input Handling', () => {
268
+ test('onReceive() passes data to Serial card', () => {
269
+ const spy = jest.spyOn(machine.io5, 'onData')
270
+ machine.onReceive(0x41)
271
+ expect(spy).toHaveBeenCalledWith(0x41)
272
+ spy.mockRestore()
273
+ })
274
+
275
+ test('onKeyDown() routes key to GPIO attachments', () => {
276
+ const matrixSpy = jest.spyOn(machine.keyboardMatrixAttachment, 'updateKey')
277
+ const encoderSpy = jest.spyOn(machine.keyboardEncoderAttachment, 'updateKey')
278
+ machine.onKeyDown(0x52) // Arrow Up USB HID keycode
279
+ expect(matrixSpy).toHaveBeenCalledWith(0x52, true)
280
+ expect(encoderSpy).toHaveBeenCalledWith(0x52, true)
281
+ matrixSpy.mockRestore()
282
+ encoderSpy.mockRestore()
283
+ })
284
+
285
+ test('onKeyUp() routes key to GPIO attachments', () => {
286
+ const matrixSpy = jest.spyOn(machine.keyboardMatrixAttachment, 'updateKey')
287
+ const encoderSpy = jest.spyOn(machine.keyboardEncoderAttachment, 'updateKey')
288
+ machine.onKeyUp(0x52) // Arrow Up USB HID keycode
289
+ expect(matrixSpy).toHaveBeenCalledWith(0x52, false)
290
+ expect(encoderSpy).toHaveBeenCalledWith(0x52, false)
291
+ matrixSpy.mockRestore()
292
+ encoderSpy.mockRestore()
293
+ })
294
+
295
+ test('onJoystick() routes button state to joystick attachment', () => {
296
+ const spy = jest.spyOn(machine.joystickAttachment, 'updateJoystick')
297
+ machine.onJoystick(0xFF)
298
+ expect(spy).toHaveBeenCalledWith(0xFF)
299
+ spy.mockRestore()
300
+ })
301
+ })
302
+
303
+ describe('Reset Operations', () => {
304
+ test('reset() resets CPU', () => {
305
+ const spy = jest.spyOn(machine.cpu, 'reset')
306
+ machine.reset(true)
307
+ expect(spy).toHaveBeenCalled()
308
+ spy.mockRestore()
309
+ })
310
+
311
+ test('reset(coldStart: true) performs full reset', () => {
312
+ machine.ram.write(0x0000, 0xFF)
313
+ expect(machine.ram.read(0x0000)).toBe(0xFF)
314
+ machine.reset(true)
315
+ expect(machine.ram.read(0x0000)).toBe(0x00)
316
+ })
317
+
318
+ test('reset(coldStart: false) performs warm reset', () => {
319
+ machine.ram.write(0x0000, 0xFF)
320
+ machine.reset(false)
321
+ // Warm reset preserves RAM
322
+ expect(machine.ram.read(0x0000)).toBe(0xFF)
323
+ })
324
+
325
+ test('reset() resets all IO cards', () => {
326
+ const io1Spy = jest.spyOn(machine.io1, 'reset')
327
+ const io2Spy = jest.spyOn(machine.io2, 'reset')
328
+ const io3Spy = jest.spyOn(machine.io3, 'reset')
329
+ const io4Spy = jest.spyOn(machine.io4, 'reset')
330
+ const io5Spy = jest.spyOn(machine.io5, 'reset')
331
+ const io6Spy = jest.spyOn(machine.io6, 'reset')
332
+ const io7Spy = jest.spyOn(machine.io7, 'reset')
333
+ const io8Spy = jest.spyOn(machine.io8, 'reset')
334
+
335
+ machine.reset(true)
336
+
337
+ expect(io1Spy).toHaveBeenCalled()
338
+ expect(io2Spy).toHaveBeenCalled()
339
+ expect(io3Spy).toHaveBeenCalled()
340
+ expect(io4Spy).toHaveBeenCalled()
341
+ expect(io5Spy).toHaveBeenCalled()
342
+ expect(io6Spy).toHaveBeenCalled()
343
+ expect(io7Spy).toHaveBeenCalled()
344
+ expect(io8Spy).toHaveBeenCalled()
345
+
346
+ io1Spy.mockRestore()
347
+ io2Spy.mockRestore()
348
+ io3Spy.mockRestore()
349
+ io4Spy.mockRestore()
350
+ io5Spy.mockRestore()
351
+ io6Spy.mockRestore()
352
+ io7Spy.mockRestore()
353
+ io8Spy.mockRestore()
354
+ })
355
+ })
356
+
357
+ describe('Callbacks', () => {
358
+ test('transmit callback can be set', () => {
359
+ const mockTransmit = jest.fn()
360
+ machine.transmit = mockTransmit
361
+ expect(machine.transmit).toBe(mockTransmit)
362
+ })
363
+
364
+ test('render callback can be set', () => {
365
+ const mockRender = jest.fn()
366
+ machine.render = mockRender
367
+ expect(machine.render).toBe(mockRender)
368
+ })
369
+
370
+ test('SerialCard uses transmit callback when set', () => {
371
+ const mockTransmit = jest.fn()
372
+ machine.transmit = mockTransmit
373
+ // Trigger SerialCard to transmit if possible
374
+ machine.io5.transmit?.(0x41)
375
+ expect(mockTransmit).toHaveBeenCalledWith(0x41)
376
+ })
377
+ })
378
+
379
+ describe('Configuration', () => {
380
+ test('Machine has configurable frequency', () => {
381
+ const originalFreq = machine.frequency
382
+ machine.frequency = 1000000
383
+ expect(machine.frequency).toBe(1000000)
384
+ machine.frequency = originalFreq
385
+ })
386
+
387
+ test('Machine has configurable scale', () => {
388
+ machine.scale = 4
389
+ expect(machine.scale).toBe(4)
390
+ })
391
+
392
+ test('Machine constants are defined', () => {
393
+ expect(Machine.MAX_FPS).toBe(60)
394
+ expect(Machine.FRAME_INTERVAL_MS).toBe(1000 / 60)
395
+ })
396
+ })
397
+
398
+ describe('Cart and ROM Interaction', () => {
399
+ test('Reading from cart address space when no cart is loaded returns 0', () => {
400
+ const cartAddress = 0xC000
401
+ expect(machine.read(cartAddress)).toBe(0)
402
+ })
403
+
404
+ test('Reading from ROM takes precedence when no cart', () => {
405
+ const romAddress = 0xA000
406
+ machine.rom.load(Array(ROM.SIZE).fill(0x00))
407
+ const result = machine.read(romAddress)
408
+ expect(typeof result).toBe('number')
409
+ })
410
+ })
411
+
412
+ describe('Memory Region Boundaries', () => {
413
+ test('Reading/writing at RAM boundaries', () => {
414
+ machine.write(RAM.START, 0x11)
415
+ expect(machine.read(RAM.START)).toBe(0x11)
416
+
417
+ machine.write(RAM.END, 0x22)
418
+ expect(machine.read(RAM.END)).toBe(0x22)
419
+ })
420
+
421
+ test('IO1 address space boundaries (0x8000-0x83FF)', () => {
422
+ machine.write(0x8000, 0x33)
423
+ expect(machine.io1.read(0)).toBe(0x33)
424
+
425
+ machine.write(0x83FF, 0x44)
426
+ expect(machine.io1.read(0x3FF)).toBe(0x44)
427
+ })
428
+
429
+ test('IO2 address space boundaries (0x8400-0x87FF)', () => {
430
+ machine.write(0x8400, 0x55)
431
+ expect(machine.io2.read(0)).toBe(0x55)
432
+
433
+ machine.write(0x87FF, 0x66)
434
+ expect(machine.io2.read(0x3FF)).toBe(0x66)
435
+ })
436
+ })
437
+ })
@@ -0,0 +1,196 @@
1
+ import { RAM } from '../components/RAM'
2
+
3
+ describe('RAM', () => {
4
+ let ram: RAM
5
+
6
+ beforeEach(() => {
7
+ ram = new RAM()
8
+ })
9
+
10
+ describe('Initialization', () => {
11
+ test('can initialize', () => {
12
+ expect(ram).not.toBeNull()
13
+ expect(ram).toBeInstanceOf(RAM)
14
+ })
15
+
16
+ test('initializes with correct size', () => {
17
+ expect(ram.data.length).toBe(RAM.SIZE)
18
+ expect(ram.data.length).toBe(0x8000) // 32KB
19
+ })
20
+
21
+ test('initializes all memory to zero', () => {
22
+ for (let i = 0; i < RAM.SIZE; i++) {
23
+ expect(ram.data[i]).toBe(0x00)
24
+ }
25
+ })
26
+ })
27
+
28
+ describe('Static Properties', () => {
29
+ test('START is 0x0000', () => {
30
+ expect(RAM.START).toBe(0x0000)
31
+ })
32
+
33
+ test('END is 0x7FFF', () => {
34
+ expect(RAM.END).toBe(0x7FFF)
35
+ })
36
+
37
+ test('SIZE is calculated correctly', () => {
38
+ expect(RAM.SIZE).toBe(0x8000)
39
+ expect(RAM.SIZE).toBe(RAM.END - RAM.START + 1)
40
+ })
41
+ })
42
+
43
+ describe('read()', () => {
44
+ test('reads zero from uninitialized address', () => {
45
+ expect(ram.read(0x0000)).toBe(0x00)
46
+ expect(ram.read(0x1234)).toBe(0x00)
47
+ expect(ram.read(0x7FFF)).toBe(0x00)
48
+ })
49
+
50
+ test('reads data from address', () => {
51
+ ram.data[0x1234] = 0x42
52
+ expect(ram.read(0x1234)).toBe(0x42)
53
+ })
54
+
55
+ test('reads from start address', () => {
56
+ ram.data[0x0000] = 0xFF
57
+ expect(ram.read(0x0000)).toBe(0xFF)
58
+ })
59
+
60
+ test('reads from end address', () => {
61
+ ram.data[0x7FFF] = 0xAB
62
+ expect(ram.read(0x7FFF)).toBe(0xAB)
63
+ })
64
+
65
+ test('reads different values from different addresses', () => {
66
+ ram.data[0x0100] = 0x11
67
+ ram.data[0x0200] = 0x22
68
+ ram.data[0x0300] = 0x33
69
+
70
+ expect(ram.read(0x0100)).toBe(0x11)
71
+ expect(ram.read(0x0200)).toBe(0x22)
72
+ expect(ram.read(0x0300)).toBe(0x33)
73
+ })
74
+ })
75
+
76
+ describe('write()', () => {
77
+ test('writes data to address', () => {
78
+ ram.write(0x1234, 0x42)
79
+ expect(ram.data[0x1234]).toBe(0x42)
80
+ })
81
+
82
+ test('writes to start address', () => {
83
+ ram.write(0x0000, 0xFF)
84
+ expect(ram.data[0x0000]).toBe(0xFF)
85
+ })
86
+
87
+ test('writes to end address', () => {
88
+ ram.write(0x7FFF, 0xAB)
89
+ expect(ram.data[0x7FFF]).toBe(0xAB)
90
+ })
91
+
92
+ test('writes different values to different addresses', () => {
93
+ ram.write(0x0100, 0x11)
94
+ ram.write(0x0200, 0x22)
95
+ ram.write(0x0300, 0x33)
96
+
97
+ expect(ram.data[0x0100]).toBe(0x11)
98
+ expect(ram.data[0x0200]).toBe(0x22)
99
+ expect(ram.data[0x0300]).toBe(0x33)
100
+ })
101
+
102
+ test('overwrites existing data', () => {
103
+ ram.write(0x1000, 0x11)
104
+ expect(ram.data[0x1000]).toBe(0x11)
105
+
106
+ ram.write(0x1000, 0x22)
107
+ expect(ram.data[0x1000]).toBe(0x22)
108
+ })
109
+
110
+ test('does not affect other addresses', () => {
111
+ ram.write(0x1000, 0xFF)
112
+
113
+ expect(ram.data[0x0FFF]).toBe(0x00)
114
+ expect(ram.data[0x1001]).toBe(0x00)
115
+ })
116
+ })
117
+
118
+ describe('read/write integration', () => {
119
+ test('read returns written value', () => {
120
+ ram.write(0x1234, 0x42)
121
+ expect(ram.read(0x1234)).toBe(0x42)
122
+ })
123
+
124
+ test('multiple read/write operations', () => {
125
+ ram.write(0x0100, 0x11)
126
+ ram.write(0x0200, 0x22)
127
+ ram.write(0x0300, 0x33)
128
+
129
+ expect(ram.read(0x0100)).toBe(0x11)
130
+ expect(ram.read(0x0200)).toBe(0x22)
131
+ expect(ram.read(0x0300)).toBe(0x33)
132
+
133
+ ram.write(0x0100, 0x44)
134
+ expect(ram.read(0x0100)).toBe(0x44)
135
+ expect(ram.read(0x0200)).toBe(0x22) // unchanged
136
+ })
137
+ })
138
+
139
+ describe('reset()', () => {
140
+ beforeEach(() => {
141
+ // Write some data to memory
142
+ ram.write(0x0000, 0x11)
143
+ ram.write(0x1234, 0x42)
144
+ ram.write(0x7FFF, 0xFF)
145
+ })
146
+
147
+ test('resets all memory to zero when coldStart is true', () => {
148
+ ram.reset(true)
149
+
150
+ expect(ram.read(0x0000)).toBe(0x00)
151
+ expect(ram.read(0x1234)).toBe(0x00)
152
+ expect(ram.read(0x7FFF)).toBe(0x00)
153
+ })
154
+
155
+ test('does not reset memory when coldStart is false', () => {
156
+ ram.reset(false)
157
+
158
+ expect(ram.read(0x0000)).toBe(0x11)
159
+ expect(ram.read(0x1234)).toBe(0x42)
160
+ expect(ram.read(0x7FFF)).toBe(0xFF)
161
+ })
162
+
163
+ test('all memory is zeroed after cold start', () => {
164
+ ram.reset(true)
165
+
166
+ for (let i = 0; i < RAM.SIZE; i++) {
167
+ expect(ram.data[i]).toBe(0x00)
168
+ }
169
+ })
170
+
171
+ test('memory can be written after reset', () => {
172
+ ram.reset(true)
173
+ ram.write(0x5000, 0xAB)
174
+
175
+ expect(ram.read(0x5000)).toBe(0xAB)
176
+ })
177
+ })
178
+
179
+ describe('Edge Cases', () => {
180
+ test('handles byte overflow (values > 0xFF)', () => {
181
+ ram.write(0x1000, 0x1FF)
182
+ expect(ram.data[0x1000]).toBe(0x1FF) // JavaScript allows this
183
+ })
184
+
185
+ test('handles zero value', () => {
186
+ ram.write(0x1000, 0xFF)
187
+ ram.write(0x1000, 0x00)
188
+ expect(ram.read(0x1000)).toBe(0x00)
189
+ })
190
+
191
+ test('handles maximum byte value (0xFF)', () => {
192
+ ram.write(0x1000, 0xFF)
193
+ expect(ram.read(0x1000)).toBe(0xFF)
194
+ })
195
+ })
196
+ })