badline 0.13.0 → 0.15.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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -1
  3. data/CLAUDE.md +42 -12
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +76 -13
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +174 -0
  8. data/doc/snapshot-format.md +1197 -0
  9. data/doc/snapshots.md +90 -40
  10. data/lib/badline/audio/bare_player.rb +46 -17
  11. data/lib/badline/audio/machine_player.rb +6 -10
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +7 -6
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +36 -4
  16. data/lib/badline/c128/drives.rb +6 -25
  17. data/lib/badline/c128/keyed_modules.rb +117 -0
  18. data/lib/badline/c128/keyed_state.rb +91 -0
  19. data/lib/badline/c128/keys.rb +9 -0
  20. data/lib/badline/c128/mmu.rb +9 -8
  21. data/lib/badline/c128/mmu_pages.rb +27 -0
  22. data/lib/badline/c128/modes.rb +19 -9
  23. data/lib/badline/c128/z80_turns.rb +1 -1
  24. data/lib/badline/c128.rb +8 -8
  25. data/lib/badline/c64/attachments.rb +0 -44
  26. data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
  27. data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
  28. data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
  29. data/lib/badline/c64/bus/debug_register.rb +32 -0
  30. data/lib/badline/c64/bus/extra_sids.rb +80 -0
  31. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  32. data/lib/badline/c64/bus.rb +3 -1
  33. data/lib/badline/c64/cartridge/action_replay.rb +6 -15
  34. data/lib/badline/c64/cartridge/comal80.rb +1 -0
  35. data/lib/badline/c64/cartridge/dinamic.rb +3 -1
  36. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  37. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  38. data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
  39. data/lib/badline/c64/cartridge/flash.rb +3 -3
  40. data/lib/badline/c64/cartridge/fun_play.rb +2 -1
  41. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  42. data/lib/badline/c64/cartridge/game_system.rb +2 -1
  43. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  44. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  45. data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
  46. data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
  47. data/lib/badline/c64/cartridge/ocean.rb +1 -0
  48. data/lib/badline/c64/cartridge/pagefox.rb +1 -10
  49. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  50. data/lib/badline/c64/cartridge/rgcd.rb +2 -11
  51. data/lib/badline/c64/cartridge/super_games.rb +3 -12
  52. data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
  53. data/lib/badline/c64/cartridge.rb +4 -30
  54. data/lib/badline/c64/kernal_traps.rb +28 -25
  55. data/lib/badline/c64/keyed_modules.rb +115 -0
  56. data/lib/badline/c64/keyed_state.rb +68 -0
  57. data/lib/badline/c64/reu/dma.rb +0 -4
  58. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  59. data/lib/badline/c64/reu/ram.rb +0 -15
  60. data/lib/badline/c64/reu.rb +1 -3
  61. data/lib/badline/c64/true_drives.rb +56 -5
  62. data/lib/badline/c64.rb +7 -3
  63. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  64. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  65. data/lib/badline/chips/cia/serial.rb +0 -4
  66. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  67. data/lib/badline/chips/cia/timer.rb +0 -20
  68. data/lib/badline/chips/cia.rb +2 -2
  69. data/lib/badline/chips/cpu/core.rb +18 -2
  70. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  71. data/lib/badline/chips/cpu/microcode.rb +16 -0
  72. data/lib/badline/chips/cpu/port.rb +20 -3
  73. data/lib/badline/chips/cpu.rb +5 -0
  74. data/lib/badline/chips/sid/decimator.rb +10 -8
  75. data/lib/badline/chips/sid/envelope.rb +1 -6
  76. data/lib/badline/chips/sid/filter.rb +0 -14
  77. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  78. data/lib/badline/chips/sid/voice.rb +0 -10
  79. data/lib/badline/chips/sid/waveform.rb +0 -2
  80. data/lib/badline/chips/sid.rb +1 -2
  81. data/lib/badline/chips/ted/beam.rb +118 -0
  82. data/lib/badline/chips/ted/fetches.rb +199 -0
  83. data/lib/badline/chips/ted/keyed_state.rb +156 -0
  84. data/lib/badline/chips/ted/modes.rb +134 -0
  85. data/lib/badline/chips/ted/palette.rb +43 -0
  86. data/lib/badline/chips/ted/picture.rb +182 -0
  87. data/lib/badline/chips/ted/sound/output.rb +86 -0
  88. data/lib/badline/chips/ted/sound.rb +284 -0
  89. data/lib/badline/chips/ted/timers.rb +66 -0
  90. data/lib/badline/chips/ted.rb +190 -0
  91. data/lib/badline/chips/tia.rb +61 -0
  92. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  93. data/lib/badline/chips/vdc/painter.rb +0 -12
  94. data/lib/badline/chips/vdc/raster.rb +0 -16
  95. data/lib/badline/chips/vdc/window.rb +0 -9
  96. data/lib/badline/chips/vdc.rb +1 -2
  97. data/lib/badline/chips/via/control_lines.rb +0 -15
  98. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  99. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  100. data/lib/badline/chips/via/shift_register.rb +0 -14
  101. data/lib/badline/chips/via/timer1.rb +0 -13
  102. data/lib/badline/chips/via/timer2.rb +0 -18
  103. data/lib/badline/chips/via.rb +1 -2
  104. data/lib/badline/chips/vic/border_mask.rb +0 -10
  105. data/lib/badline/chips/vic/collisions.rb +0 -11
  106. data/lib/badline/chips/vic/color_patches.rb +0 -11
  107. data/lib/badline/chips/vic/display_state.rb +0 -19
  108. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  109. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  110. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  111. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  112. data/lib/badline/chips/vic/register_log.rb +0 -18
  113. data/lib/badline/chips/vic/registers.rb +0 -9
  114. data/lib/badline/chips/vic/sequencer.rb +0 -2
  115. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  116. data/lib/badline/chips/vic/sprite.rb +0 -2
  117. data/lib/badline/chips/vic/sprites.rb +0 -3
  118. data/lib/badline/chips/vic.rb +6 -2
  119. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  120. data/lib/badline/chips/vic_i/painter.rb +0 -4
  121. data/lib/badline/chips/vic_i/sound.rb +38 -46
  122. data/lib/badline/chips/vic_i.rb +2 -2
  123. data/lib/badline/chips/z80/core.rb +2 -2
  124. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  125. data/lib/badline/chips.rb +10 -0
  126. data/lib/badline/core.rb +4 -21
  127. data/lib/badline/drives/datasette/keyed_state.rb +43 -0
  128. data/lib/badline/drives/datasette.rb +62 -12
  129. data/lib/badline/drives/engine/core.rb +3 -0
  130. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  131. data/lib/badline/drives/model1541/bus.rb +0 -13
  132. data/lib/badline/drives/model1541/disk.rb +9 -8
  133. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  134. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  135. data/lib/badline/drives/model1541.rb +1 -2
  136. data/lib/badline/drives/model1571/bus.rb +0 -13
  137. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  138. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  139. data/lib/badline/drives/model1571.rb +1 -2
  140. data/lib/badline/drives/model1581/bus.rb +0 -13
  141. data/lib/badline/drives/model1581/disk.rb +0 -4
  142. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  143. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  144. data/lib/badline/drives/model1581/slot.rb +0 -18
  145. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  146. data/lib/badline/drives/model1581.rb +2 -2
  147. data/lib/badline/drives.rb +8 -0
  148. data/lib/badline/frontend/app.rb +3 -3
  149. data/lib/badline/frontend/boot.rb +4 -2
  150. data/lib/badline/frontend/controls.rb +25 -5
  151. data/lib/badline/frontend/drive_led.rb +44 -21
  152. data/lib/badline/frontend/keys.rb +14 -2
  153. data/lib/badline/frontend/media_slots.rb +11 -1
  154. data/lib/badline/frontend/menu/menu_dialogs.rb +4 -3
  155. data/lib/badline/frontend/menu/menu_media.rb +13 -3
  156. data/lib/badline/frontend/menu/menu_pages.rb +8 -8
  157. data/lib/badline/frontend/menu/menu_ports.rb +13 -1
  158. data/lib/badline/frontend/screen.rb +33 -12
  159. data/lib/badline/frontend/screens.rb +2 -1
  160. data/lib/badline/frontend/snapshots.rb +12 -1
  161. data/lib/badline/frontend/sound.rb +4 -4
  162. data/lib/badline/frontend/timeline.rb +43 -12
  163. data/lib/badline/input/matrix_typist.rb +98 -0
  164. data/lib/badline/input.rb +1 -0
  165. data/lib/badline/kernal_trap/chrout.rb +45 -0
  166. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  167. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  168. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  169. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  170. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  171. data/lib/badline/kernal_trap/dos.rb +0 -19
  172. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  173. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  174. data/lib/badline/kernal_trap/layout.rb +75 -11
  175. data/lib/badline/kernal_trap/load.rb +14 -16
  176. data/lib/badline/kernal_trap/routine.rb +12 -0
  177. data/lib/badline/kernal_trap/save.rb +8 -18
  178. data/lib/badline/kernal_trap/serial.rb +1 -17
  179. data/lib/badline/kernal_trap.rb +2 -0
  180. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  181. data/lib/badline/machine/settling.rb +29 -0
  182. data/lib/badline/machine.rb +29 -6
  183. data/lib/badline/media/boot_disk.rb +14 -2
  184. data/lib/badline/media/extensions.rb +10 -0
  185. data/lib/badline/media/plus4_media.rb +68 -0
  186. data/lib/badline/media/program.rb +20 -6
  187. data/lib/badline/media.rb +33 -12
  188. data/lib/badline/memory.rb +3 -6
  189. data/lib/badline/options/family.rb +6 -2
  190. data/lib/badline/options/help.rb +66 -16
  191. data/lib/badline/options/table.rb +17 -2
  192. data/lib/badline/options/validation.rb +26 -2
  193. data/lib/badline/options.rb +42 -9
  194. data/lib/badline/plus4/acia.rb +45 -0
  195. data/lib/badline/plus4/bus.rb +375 -0
  196. data/lib/badline/plus4/cassette_sense.rb +21 -0
  197. data/lib/badline/plus4/cpu.rb +10 -0
  198. data/lib/badline/plus4/cpu_port.rb +93 -0
  199. data/lib/badline/plus4/keyboard_port.rb +76 -0
  200. data/lib/badline/plus4/keyed_modules.rb +99 -0
  201. data/lib/badline/plus4/keyed_parts.rb +143 -0
  202. data/lib/badline/plus4/keyed_state.rb +41 -0
  203. data/lib/badline/plus4/model.rb +38 -0
  204. data/lib/badline/plus4/user_port.rb +31 -0
  205. data/lib/badline/plus4.rb +240 -0
  206. data/lib/badline/roms/README +38 -0
  207. data/lib/badline/roms/plus4/basic.rom +0 -0
  208. data/lib/badline/roms/plus4/function-high.rom +0 -0
  209. data/lib/badline/roms/plus4/function-low.rom +0 -0
  210. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  211. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  212. data/lib/badline/snapshot/c128_setup.rb +9 -20
  213. data/lib/badline/snapshot/c64_setup.rb +28 -24
  214. data/lib/badline/snapshot/container.rb +6 -0
  215. data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
  216. data/lib/badline/snapshot/field_doc.rb +148 -0
  217. data/lib/badline/snapshot/image.rb +143 -28
  218. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  219. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  220. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  221. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  222. data/lib/badline/snapshot/keyed/report.rb +26 -0
  223. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  224. data/lib/badline/snapshot/keyed.rb +64 -0
  225. data/lib/badline/snapshot/machine_state.rb +17 -64
  226. data/lib/badline/snapshot/plus4_setup.rb +39 -0
  227. data/lib/badline/snapshot/storage_setup.rb +29 -34
  228. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  229. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  230. data/lib/badline/snapshot/vice.rb +7 -31
  231. data/lib/badline/snapshot.rb +48 -20
  232. data/lib/badline/storage/crt_file.rb +3 -1
  233. data/lib/badline/storage/g64_image.rb +9 -4
  234. data/lib/badline/storage/tap.rb +57 -20
  235. data/lib/badline/support/checkpoint.rb +3 -2
  236. data/lib/badline/support/plus4_checkpoint.rb +42 -0
  237. data/lib/badline/version.rb +1 -1
  238. data/lib/badline/vic20/attachments.rb +3 -0
  239. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  240. data/lib/badline/vic20/bus.rb +1 -2
  241. data/lib/badline/vic20/keyed_modules.rb +73 -0
  242. data/lib/badline/vic20/keyed_state.rb +45 -0
  243. data/lib/badline/vic20.rb +5 -5
  244. data/lib/badline.rb +1 -0
  245. metadata +75 -42
  246. data/lib/badline/c128/bus/saved_state.rb +0 -53
  247. data/lib/badline/c128/saved_state.rb +0 -117
  248. data/lib/badline/c64/bus/saved_state.rb +0 -68
  249. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  250. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  251. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  252. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  253. data/lib/badline/c64/reu/saved_state.rb +0 -55
  254. data/lib/badline/c64/saved_state.rb +0 -112
  255. data/lib/badline/chips/cia/saved_state.rb +0 -49
  256. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  257. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  258. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  259. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  260. data/lib/badline/chips/sid/saved_state.rb +0 -52
  261. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  262. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  263. data/lib/badline/chips/via/saved_state.rb +0 -44
  264. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  265. data/lib/badline/chips/vic/saved_state.rb +0 -82
  266. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  267. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  268. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  269. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  270. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  271. data/lib/badline/chips/z80/saved_state.rb +0 -71
  272. data/lib/badline/chrout_trap.rb +0 -43
  273. data/lib/badline/debug_register.rb +0 -28
  274. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  275. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  276. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  277. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  278. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  279. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  280. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  281. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  282. data/lib/badline/keyboard_buffer.rb +0 -33
  283. data/lib/badline/snapshot/setup.rb +0 -31
  284. data/lib/badline/snapshot/state.rb +0 -179
  285. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  286. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -147,25 +147,6 @@ module Badline
147
147
  # vector, as U: does.
148
148
  def reset! = reset(cold: true)
149
149
 
150
- # The mounted storage's setup (Snapshot::StorageSetup), then the drive's
151
- # RAM, its status and its open channels.
152
- def save_state(out)
153
- Snapshot::StorageSetup.write(@storage, out)
154
- out.marker("TRAP DRIVE")
155
- @memory.save_state(out)
156
- @status.save_state(out)
157
- @channels.save_state(out)
158
- end
159
-
160
- # Takes the state after the storage's setup, into a drive mounted on
161
- # the reopened storage.
162
- def load_state(input)
163
- input.marker("TRAP DRIVE")
164
- @memory.load_state(input)
165
- @status.load_state(input)
166
- @channels.load_state(input, @memory)
167
- end
168
-
169
150
  # Returns the next byte and whether it is the channel's last, or nil
170
151
  # when there is nothing left to send.
171
152
  def read(secondary)
@@ -6,17 +6,17 @@ module Badline
6
6
  private
7
7
 
8
8
  def active?
9
- kernal_mapped? && @bus.peek(0xba) == DEVICE
9
+ kernal_mapped? && @bus.peek(zero_page.device) == DEVICE
10
10
  end
11
11
 
12
12
  def filename
13
13
  Storage.strip_drive_prefix(full_filename)
14
14
  end
15
15
 
16
- # Filename pointer at $BB/$BC, length at $B7
16
+ # The file name FNADR points to, FNLEN bytes long.
17
17
  def full_filename
18
- pointer = uint16(@bus.peek(0xbb), @bus.peek(0xbc))
19
- bytes = Array.new(@bus.peek(0xb7)) do |i|
18
+ pointer = peek_word(zero_page.filename_address)
19
+ bytes = Array.new(@bus.peek(zero_page.filename_length)) do |i|
20
20
  @layout.filename_byte(@bus, (pointer + i) & 0xffff)
21
21
  end
22
22
  Storage.ascii(bytes)
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module KernalTrap
5
+ class DOS
6
+ class Channel
7
+ # A channel's fields under `prefix`: whether it is a buffer in the
8
+ # drive's RAM, where it starts and ends, the pointer, the byte it
9
+ # leads with and its buffer's number, the error it reports once its
10
+ # bytes run out, and a file's bytes.
11
+ def save_fields(out, prefix)
12
+ out.text("#{prefix}kind", "channel").bool("#{prefix}writable", @writable).int("#{prefix}base", @base)
13
+ out.int("#{prefix}length", @length).int("#{prefix}pointer", @pointer).optional_int("#{prefix}lead", @lead)
14
+ out.optional_int("#{prefix}buffer", @buffer)
15
+ error = @error
16
+ out.ints("#{prefix}error", error) if error
17
+ out.bytes("#{prefix}bytes", @bytes) unless @writable
18
+ end
19
+
20
+ # A buffer channel's bytes are the drive's `ram`.
21
+ def load_fields(input, prefix, ram)
22
+ @writable = input.bool("#{prefix}writable", false)
23
+ @base = input.int("#{prefix}base", 0)
24
+ @length = input.int("#{prefix}length", 0)
25
+ @pointer = input.int("#{prefix}pointer", 0)
26
+ @lead = input.optional_int("#{prefix}lead")
27
+ @buffer = input.optional_int("#{prefix}buffer")
28
+ error = input.ints("#{prefix}error", [])
29
+ @error = error.empty? ? nil : error.dup
30
+ @bytes = @writable ? ram : input.bytes("#{prefix}bytes", "".b).bytes
31
+ self
32
+ end
33
+ end
34
+
35
+ class WriteFile
36
+ # A file open for writing, under `prefix`: its name, type and mode,
37
+ # the bytes written so far and the byte held back.
38
+ def save_fields(out, prefix)
39
+ out.text("#{prefix}kind", "write_file").bytes("#{prefix}name", @name.b).text("#{prefix}type", @type.to_s)
40
+ out.text("#{prefix}mode", @mode.to_s).bytes("#{prefix}bytes", @bytes).optional_int("#{prefix}held", @buffer)
41
+ end
42
+
43
+ def self.from_fields(input, prefix)
44
+ type = input.choice("#{prefix}type", TYPES.map(&:to_s), "prg")
45
+ mode = input.choice("#{prefix}mode", MODES.map(&:to_s), "write")
46
+ new(input.bytes("#{prefix}name", "".b), TYPES.find { |each| each.to_s == type } || :prg,
47
+ mode: MODES.find { |each| each.to_s == mode } || :write).tap do |file|
48
+ file.load_fields(input, prefix)
49
+ end
50
+ end
51
+
52
+ def load_fields(input, prefix)
53
+ @bytes = input.bytes("#{prefix}bytes", "".b).bytes
54
+ @buffer = input.optional_int("#{prefix}held")
55
+ end
56
+ end
57
+
58
+ class Channels
59
+ # The channels open, by secondary address, each under channel<SA>.
60
+ def save_fields(out)
61
+ out.ints("channels", @channels.keys)
62
+ @channels.each { |secondary, channel| channel.save_fields(out, "channel#{secondary}.") }
63
+ end
64
+
65
+ def load_fields(input, ram)
66
+ @channels.clear
67
+ input.ints("channels", []).each do |secondary|
68
+ @channels[secondary] = Channels.channel_from_fields(input, "channel#{secondary}.", ram)
69
+ end
70
+ end
71
+
72
+ def self.channel_from_fields(input, prefix, ram)
73
+ return WriteFile.from_fields(input, prefix) if input.text("#{prefix}kind", "channel") == "write_file"
74
+
75
+ Channel.new.load_fields(input, prefix, ram)
76
+ end
77
+ end
78
+
79
+ class Status
80
+ # The error channel, under status.
81
+ def save_fields(out) = @channel.save_fields(out, "status.")
82
+
83
+ def load_fields(input)
84
+ input.choice("status.kind", ["channel"], "channel")
85
+ @channel = Channel.new.load_fields(input, "status.", [])
86
+ end
87
+ end
88
+
89
+ class Memory
90
+ # The drive's RAM, and whether the BAM has been read.
91
+ def save_fields(out)
92
+ out.bytes("ram", @ram).bool("bam", @bam)
93
+ end
94
+
95
+ def load_fields(input)
96
+ input.bytes_into("ram", @ram)
97
+ @bam = input.bool("bam", @bam)
98
+ end
99
+ end
100
+
101
+ # The storage device 8 serves.
102
+ attr_reader :storage
103
+
104
+ # The DOS's fields in a keyed snapshot module (BL-TRAPDOS), after the
105
+ # storage's under storage.*: its RAM, the error channel and the
106
+ # channels open.
107
+ def save_fields(out)
108
+ @memory.save_fields(out)
109
+ @status.save_fields(out)
110
+ @channels.save_fields(out)
111
+ end
112
+
113
+ def load_fields(input)
114
+ @memory.load_fields(input)
115
+ @status.load_fields(input)
116
+ @channels.load_fields(input, @memory.ram)
117
+ end
118
+ end
119
+
120
+ class Serial
121
+ # The serial trap's fields under `prefix`: whether device 8 listens
122
+ # or talks, on which channel, the frame it is in and the bytes it has
123
+ # gathered.
124
+ def save_fields(out, prefix)
125
+ out.bool("#{prefix}listening", @listening).bool("#{prefix}talking", @talking)
126
+ out.optional_int("#{prefix}listen_channel", @listen_channel)
127
+ out.optional_int("#{prefix}talk_channel", @talk_channel).int("#{prefix}frame", @frame)
128
+ out.bytes("#{prefix}buffer", @buffer)
129
+ end
130
+
131
+ def load_fields(input, prefix)
132
+ @listening = input.bool("#{prefix}listening", @listening)
133
+ @talking = input.bool("#{prefix}talking", @talking)
134
+ @listen_channel = input.optional_int("#{prefix}listen_channel")
135
+ @talk_channel = input.optional_int("#{prefix}talk_channel")
136
+ @frame = input.int("#{prefix}frame", @frame)
137
+ @buffer = input.bytes("#{prefix}buffer", "".b).bytes
138
+ end
139
+ end
140
+
141
+ class Save
142
+ # Whether a SAVE is under way, under `prefix`.
143
+ def save_fields(out, prefix) = out.bool("#{prefix}saving", @saving)
144
+
145
+ def load_fields(input, prefix)
146
+ @saving = input.bool("#{prefix}saving", @saving)
147
+ end
148
+ end
149
+ end
150
+ end
@@ -20,7 +20,11 @@ module Badline
20
20
  # +banked+ says whether the 6510 port can bank the KERNAL out, and so
21
21
  # whether RAM lies under every ROM and the I/O. +mmu+ says the C128's
22
22
  # MMU maps the KERNAL instead, and that the file name and the file's
23
- # bytes lie in the banks the KERNAL keeps at FNBANK and BA. Each
23
+ # bytes lie in the banks the KERNAL keeps at FNBANK and BA.
24
+ # +rom_select+ says the 264 series' TED selects ROM or RAM and its
25
+ # latch the ROM banks, so the KERNAL is in while ROM is selected with
26
+ # the KERNAL's bank, and the ROM reads the file name and the file's
27
+ # bytes from RAM, under any ROM, through its bank fetch at $07D9. Each
24
28
  # serial byte starts a one-shot timer by writing the high byte of its
25
29
  # count to +serial_timer+, and the control value TIMER_ONE_SHOT_START
26
30
  # to +serial_timer_start+ where the timer has a control register to
@@ -29,16 +33,19 @@ module Badline
29
33
  # +serial_timer+. The ROM lets go of each serial line by reading a
30
34
  # port, masking the line's bit off and writing it back: ATN at
31
35
  # +atn_port+ with +atn_mask+, then the clock and the data line.
36
+ # +zero_page+ is a ZeroPage, where the KERNAL keeps the file's
37
+ # parameters and the status the traps read and set.
32
38
  Layout = Data.define(:load, :save, :searching_message, :loading_message, :load_byte_loop, :load_done,
33
39
  :file_not_found_exit, :missing_file_name_exit, :saving_message, :clock_release,
34
40
  :data_release, :save_done, :talk, :listen, :second, :tksa, :ciout, :untalk,
35
41
  :unlisten, :acptr, :banked, :serial_timer, :serial_timer_start, :atn_port, :atn_mask,
36
- :clock_port, :clock_mask, :data_port, :data_mask, :mmu) do
42
+ :clock_port, :clock_mask, :data_port, :data_mask, :mmu, :rom_select, :zero_page) do
37
43
  # Whether the CPU reads the KERNAL ROM at the trapped addresses: on a
38
- # banked machine, the 6510 port's banking bits select it, and on the
39
- # C128 the MMU.
44
+ # banked machine, the 6510 port's banking bits select it, on the
45
+ # C128 the MMU, and on the 264 series the ROM select and bank latch.
40
46
  def kernal_mapped?(bus)
41
47
  return bus.system_rom_mapped? if mmu
48
+ return bus.kernal_selected? if rom_select
42
49
 
43
50
  !banked || bus.io_port.kernal?
44
51
  end
@@ -55,15 +62,21 @@ module Badline
55
62
  end
56
63
 
57
64
  # The file name's byte at +address+, in the bank at FNBANK on the
58
- # C128.
65
+ # C128 and in RAM on the 264 series.
59
66
  def filename_byte(bus, address)
60
- mmu ? bus.peek_bank(bus.peek(FILENAME_BANK), address) : bus.peek(address)
67
+ return bus.peek_bank(bus.peek(FILENAME_BANK), address) if mmu
68
+ return bus.ram_view.peek(address) if rom_select
69
+
70
+ bus.peek(address)
61
71
  end
62
72
 
63
73
  # The byte at +address+ a SAVE writes or a VERIFY compares, in the bank
64
- # at BA on the C128.
74
+ # at BA on the C128 and in RAM on the 264 series.
65
75
  def file_byte(bus, address)
66
- mmu ? bus.peek_bank(bus.peek(BANK), address) : bus.peek(address)
76
+ return bus.peek_bank(bus.peek(BANK), address) if mmu
77
+ return bus.ram_view.peek(address) if rom_select
78
+
79
+ bus.peek(address)
67
80
  end
68
81
 
69
82
  # Starts the timer the ROM times a serial byte with, its count's high
@@ -95,6 +108,32 @@ module Badline
95
108
  end
96
109
  end
97
110
 
111
+ # The zero-page variables of a KERNAL's file routines, by address:
112
+ # +status+ (ST), +verify+ (VERCK, 0 for LOAD and 1 for VERIFY),
113
+ # +start_address+ (SAL), +end_address+ (EAL), +filename_length+
114
+ # (FNLEN), +secondary_address+ (SA), +device+ (FA), +filename_address+
115
+ # (FNADR), +save_start+ (STAL) and +load_address+ (MEMUSS, where LOAD
116
+ # puts a file loaded with secondary address 0). Each two-byte one names
117
+ # its low byte, and the high byte follows it.
118
+ ZeroPage = Data.define(:status, :verify, :start_address, :end_address, :filename_length, :secondary_address,
119
+ :device, :filename_address, :save_start, :load_address)
120
+
121
+ # The C64's file variables. The VIC-20 and C128 KERNALs keep theirs at
122
+ # the same addresses.
123
+ C64_ZERO_PAGE = ZeroPage.new(
124
+ status: 0x90, verify: 0x93, start_address: 0xac, end_address: 0xae, filename_length: 0xb7,
125
+ secondary_address: 0xb9, device: 0xba, filename_address: 0xbb, save_start: 0xc1, load_address: 0xc3
126
+ )
127
+
128
+ # The 264 series KERNAL's file variables, as its LOAD ($F04A) and SAVE
129
+ # ($F1A4) use them: SAVE walks SAL at $9B from STAL at $B2 up to EAL at
130
+ # $9D, and LOAD ends with EAL at $9D, or starts it from MEMUSS at $B4
131
+ # for secondary address 0.
132
+ PLUS4_ZERO_PAGE = ZeroPage.new(
133
+ status: 0x90, verify: 0x93, start_address: 0x9b, end_address: 0x9d, filename_length: 0xab,
134
+ secondary_address: 0xad, device: 0xae, filename_address: 0xaf, save_start: 0xb2, load_address: 0xb4
135
+ )
136
+
98
137
  # A CIA's control value that starts its timer: force load, one-shot,
99
138
  # start.
100
139
  TIMER_ONE_SHOT_START = 0x19
@@ -117,7 +156,7 @@ module Badline
117
156
  ciout: 0xeddd, untalk: 0xedef, unlisten: 0xedfe, acptr: 0xee13,
118
157
  banked: true, serial_timer: 0xdc07, serial_timer_start: 0xdc0f,
119
158
  atn_port: 0xdd00, atn_mask: 0xf7, clock_port: 0xdd00, clock_mask: 0xef, data_port: 0xdd00, data_mask: 0xdf,
120
- mmu: false
159
+ mmu: false, rom_select: false, zero_page: C64_ZERO_PAGE
121
160
  )
122
161
 
123
162
  # The VIC-20 KERNAL, 901486-07 (PAL). The NTSC one, 901486-06, differs
@@ -135,7 +174,7 @@ module Badline
135
174
  ciout: 0xeee4, untalk: 0xeef6, unlisten: 0xef04, acptr: 0xef19,
136
175
  banked: false, serial_timer: 0x9129, serial_timer_start: nil,
137
176
  atn_port: 0x911f, atn_mask: 0x7f, clock_port: 0x912c, clock_mask: 0xfd, data_port: 0x912c, data_mask: 0xdf,
138
- mmu: false
177
+ mmu: false, rom_select: false, zero_page: C64_ZERO_PAGE
139
178
  )
140
179
 
141
180
  # The C128 KERNAL, 318020-05, in C128 mode: the jump table's targets
@@ -156,7 +195,32 @@ module Badline
156
195
  ciout: 0xe503, untalk: 0xe515, unlisten: 0xe526, acptr: 0xe43e,
157
196
  banked: true, serial_timer: nil, serial_timer_start: nil,
158
197
  atn_port: 0xdd00, atn_mask: 0xf7, clock_port: 0xdd00, clock_mask: 0xef, data_port: 0xdd00, data_mask: 0xdf,
159
- mmu: true
198
+ mmu: true, rom_select: false, zero_page: C64_ZERO_PAGE
199
+ )
200
+
201
+ # The 264 series KERNAL, 318004-05 (PAL), the same at every address
202
+ # here in the NTSC 318005-05: ILOAD's and ISAVE's default targets and
203
+ # the jump table's targets for the serial primitives, each of which
204
+ # tries a 1551's TCBM interface for devices 8 and 9 before the serial
205
+ # bus. LOAD prints SEARCHING FOR at $F160 and LOADING or VERIFYING at
206
+ # $F189, which ends CLC, retries a byte from $F09F and returns at $F0EB
207
+ # (LDX $9D, LDY $9E, RTS). FILE NOT FOUND leaves through $F27C and
208
+ # MISSING FILE NAME through $F288. SAVE prints SAVING at $F228 and
209
+ # returns at $F226 (CLC, RTS). The port at $01 drives the lines, ATN
210
+ # from bit 2, the clock from bit 1 ($E2B8 lets it go) and the data line
211
+ # from bit 0 ($E2C6). Its serial bytes are timed with TED timer 2, but
212
+ # its timeouts differ from the C64's, so the traps leave the timer
213
+ # alone.
214
+ PLUS4_LAYOUT = Layout.new(
215
+ load: 0xf04a, save: 0xf1a4,
216
+ searching_message: 0xf160, loading_message: 0xf189, load_byte_loop: 0xf09f, load_done: 0xf0eb,
217
+ file_not_found_exit: 0xf27c, missing_file_name_exit: 0xf288,
218
+ saving_message: 0xf228, clock_release: 0xe2b8, data_release: 0xe2c6, save_done: 0xf226,
219
+ talk: 0xedfa, listen: 0xee2c, second: 0xee4d, tksa: 0xee1a,
220
+ ciout: 0xecdf, untalk: 0xef3b, unlisten: 0xef23, acptr: 0xec8b,
221
+ banked: false, serial_timer: nil, serial_timer_start: nil,
222
+ atn_port: 0x01, atn_mask: 0xfb, clock_port: 0x01, clock_mask: 0xfd, data_port: 0x01, data_mask: 0xfe,
223
+ mmu: false, rom_select: true, zero_page: PLUS4_ZERO_PAGE
160
224
  )
161
225
  end
162
226
  end
@@ -31,8 +31,8 @@ module Badline
31
31
  def call
32
32
  return unless active?
33
33
 
34
- @bus.poke(0x93, @cpu.a)
35
- @bus.poke(0x90, 0x00)
34
+ @bus.poke(zero_page.verify, @cpu.a)
35
+ @bus.poke(zero_page.status, 0x00)
36
36
  name = filename
37
37
  return @cpu.program_counter = @layout.missing_file_name_exit if Storage.parse_name(name).first.empty?
38
38
 
@@ -66,17 +66,17 @@ module Badline
66
66
  # until RUN/STOP breaks the load, as on a real drive.
67
67
  def finish(data, complete)
68
68
  if data.empty?
69
- @bus.poke(0x90, EOI | READ_TIMEOUT)
69
+ @bus.poke(zero_page.status, EOI | READ_TIMEOUT)
70
70
  leave_bus(ACPTR_TIMEOUT)
71
71
  continue_with(@layout.searching_message, @layout.file_not_found_exit)
72
72
  else
73
73
  deliver(data)
74
- @bus.poke(0x90, @bus.peek(0x90) & ~EOI) unless complete
74
+ @bus.poke(zero_page.status, @bus.peek(zero_page.status) & ~EOI) unless complete
75
75
  leave_bus(ISOUR_TIMEOUT)
76
76
  continue_with(@layout.searching_message, @layout.loading_message,
77
77
  complete ? @layout.load_done : @layout.load_byte_loop)
78
78
  end
79
- @bus.poke(0xb9, LOAD_SECONDARY)
79
+ @bus.poke(zero_page.secondary_address, LOAD_SECONDARY)
80
80
  end
81
81
 
82
82
  # A load ends with the UNLISTEN that closes the file, a byte sent,
@@ -91,9 +91,9 @@ module Badline
91
91
  load? && data.length > 2 && load_address(data) < 0x0334
92
92
  end
93
93
 
94
- # A=0 is LOAD, A=1 is VERIFY, kept at $93 (VERCK)
94
+ # A=0 is LOAD, A=1 is VERIFY, kept at VERCK
95
95
  def load?
96
- @bus.peek(0x93).zero?
96
+ @bus.peek(zero_page.verify).zero?
97
97
  end
98
98
 
99
99
  def deliver(data)
@@ -102,25 +102,23 @@ module Badline
102
102
  payload = payload[0, 0x10000 - addr] if addr + payload.length > 0x10000
103
103
  if load?
104
104
  @layout.store_file(@bus, addr, payload)
105
- @bus.poke(0x90, EOI)
105
+ @bus.poke(zero_page.status, EOI)
106
106
  else
107
107
  verify(addr, payload)
108
108
  end
109
- end_addr = (addr + payload.length) & 0xffff
110
- @bus.poke(0xae, low_byte(end_addr))
111
- @bus.poke(0xaf, high_byte(end_addr))
109
+ poke_word(zero_page.end_address, (addr + payload.length) & 0xffff)
112
110
  end
113
111
 
114
112
  def verify(addr, payload)
115
113
  match = payload.each_with_index.all? { |byte, i| @layout.file_byte(@bus, addr + i) == byte }
116
- @bus.poke(0x90, match ? EOI : EOI | VERIFY_MISMATCH)
114
+ @bus.poke(zero_page.status, match ? EOI : EOI | VERIFY_MISMATCH)
117
115
  end
118
116
 
119
- # Secondary address $B9 zero relocates to the caller-supplied address
120
- # stashed at $C3/$C4 (MEMUSS)
117
+ # Secondary address zero relocates to the caller-supplied address
118
+ # stashed at MEMUSS
121
119
  def load_address(data)
122
- if @bus.peek(0xb9).zero?
123
- uint16(@bus.peek(0xc3), @bus.peek(0xc4))
120
+ if @bus.peek(zero_page.secondary_address).zero?
121
+ peek_word(zero_page.load_address)
124
122
  else
125
123
  uint16(data[0], data[1].to_i)
126
124
  end
@@ -26,6 +26,18 @@ module Badline
26
26
  @layout.kernal_mapped?(@bus)
27
27
  end
28
28
 
29
+ def zero_page = @layout.zero_page
30
+
31
+ # The two-byte value whose low byte is at +address+.
32
+ def peek_word(address)
33
+ uint16(@bus.peek(address), @bus.peek(address + 1))
34
+ end
35
+
36
+ def poke_word(address, value)
37
+ @bus.poke(address, low_byte(value))
38
+ @bus.poke(address + 1, high_byte(value))
39
+ end
40
+
29
41
  # The ROM times each byte on the serial bus with a one-shot timer
30
42
  # (Layout#time_serial_byte): ISOUR_TIMEOUT for a byte sent,
31
43
  # ACPTR_TIMEOUT for one received.
@@ -18,7 +18,7 @@ module Badline
18
18
  class Save < FileRoutine
19
19
  SECONDARY = 0x61
20
20
 
21
- # ST bit at $90
21
+ # ST bit
22
22
  DEVICE_NOT_PRESENT = 0x80
23
23
 
24
24
  def initialize(cpu:, bus:, layout:, dos:)
@@ -27,23 +27,15 @@ module Badline
27
27
  @saving = false
28
28
  end
29
29
 
30
- def save_state(out)
31
- out.boolean(@saving)
32
- end
33
-
34
- def load_state(input)
35
- @saving = input.boolean?
36
- end
37
-
38
30
  def call
39
31
  return unless active?
40
32
  return finish if @saving
41
33
  return unless @dos.saves?
42
34
 
43
- @bus.poke(0xb9, SECONDARY)
35
+ @bus.poke(zero_page.secondary_address, SECONDARY)
44
36
  return @cpu.program_counter = @layout.missing_file_name_exit if name.empty?
45
37
 
46
- @bus.poke(0x90, 0x00)
38
+ @bus.poke(zero_page.status, 0x00)
47
39
  @saving = true
48
40
  continue_with(@layout.saving_message, @layout.save)
49
41
  end
@@ -57,19 +49,17 @@ module Badline
57
49
  def finish
58
50
  @saving = false
59
51
  saved = @dos.save(name, payload, replace: full_filename.start_with?("@"))
60
- @bus.poke(0x90, DEVICE_NOT_PRESENT) unless saved
61
- @bus.poke(0xac, @bus.peek(0xae))
62
- @bus.poke(0xad, @bus.peek(0xaf))
52
+ @bus.poke(zero_page.status, DEVICE_NOT_PRESENT) unless saved
53
+ poke_word(zero_page.start_address, peek_word(zero_page.end_address))
63
54
  @cpu.y = 0
64
55
  @cpu.status.overflow = true
65
56
  continue_with(@layout.clock_release, @layout.data_release, @layout.save_done)
66
57
  end
67
58
 
68
- # Start address at $C1/$C2 (STAL), end address (exclusive) at
69
- # $AE/$AF (EAL)
59
+ # Start address at STAL, end address (exclusive) at EAL
70
60
  def payload
71
- start = uint16(@bus.peek(0xc1), @bus.peek(0xc2))
72
- length = (uint16(@bus.peek(0xae), @bus.peek(0xaf)) - start) & 0xffff
61
+ start = peek_word(zero_page.save_start)
62
+ length = (peek_word(zero_page.end_address) - start) & 0xffff
73
63
  [low_byte(start), high_byte(start)] +
74
64
  Array.new(length) { |i| @layout.file_byte(@bus, (start + i) & 0xffff) }
75
65
  end
@@ -35,22 +35,6 @@ module Badline
35
35
  @buffer = []
36
36
  end
37
37
 
38
- # The frame under way, with the channels and the bytes it gathered.
39
- # The device it answers is the machine's to set.
40
- def save_state(out)
41
- out.boolean(@listening).boolean(@talking).optional_int(@listen_channel).optional_int(@talk_channel)
42
- out.int(@frame).blob(@buffer)
43
- end
44
-
45
- def load_state(input)
46
- @listening = input.boolean?
47
- @talking = input.boolean?
48
- @listen_channel = input.optional_int
49
- @talk_channel = input.optional_int
50
- @frame = input.int
51
- @buffer = input.blob
52
- end
53
-
54
38
  def install
55
39
  routines.each do |address, routine|
56
40
  @cpu.install_trap(address) { call_routine(routine) if kernal_mapped? }
@@ -205,7 +189,7 @@ module Badline
205
189
  def update_status(bits)
206
190
  return if bits.zero?
207
191
 
208
- @bus.poke(0x90, @bus.peek(0x90) | bits)
192
+ @bus.poke(zero_page.status, @bus.peek(zero_page.status) | bits)
209
193
  end
210
194
  end
211
195
  end
@@ -7,3 +7,5 @@ require "badline/kernal_trap/file_routine"
7
7
  require "badline/kernal_trap/load"
8
8
  require "badline/kernal_trap/save"
9
9
  require "badline/kernal_trap/serial"
10
+ require "badline/kernal_trap/chrout"
11
+ require "badline/kernal_trap/keyed_state"
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Machine
5
+ # Typing into the KERNAL's keyboard buffer once the machine has booted.
6
+ # The machine that includes it answers #keyboard_buffer, the Layout of
7
+ # the KERNAL it runs, and #ram.
8
+ module KeyboardBuffer
9
+ # Where a KERNAL keeps its keyboard buffer (KEYD), the count of
10
+ # characters in it (NDX), and how many it holds.
11
+ Layout = Data.define(:address, :count, :capacity)
12
+
13
+ # The C64's and the VIC-20's.
14
+ C64_LAYOUT = Layout.new(address: 0x0277, count: 0xc6, capacity: 10)
15
+
16
+ # BASIC 7.0's, in C128 mode.
17
+ C128_LAYOUT = Layout.new(address: 0x034a, count: 0xd0, capacity: 10)
18
+
19
+ # The 264 series KERNAL's, which sets its capacity at $053F to 10
20
+ # ($D86E).
21
+ PLUS4_LAYOUT = Layout.new(address: 0x0527, count: 0xef, capacity: 10)
22
+
23
+ def type_text(text)
24
+ codes = text.bytes.map { |b| ascii_to_petscii(b) }
25
+ on_init { @pending_keys = codes }
26
+ end
27
+
28
+ private
29
+
30
+ def feed_keyboard
31
+ buffer = keyboard_buffer
32
+ return unless ram.peek(buffer.count).zero?
33
+
34
+ chunk = @pending_keys.shift(buffer.capacity)
35
+ ram.write(buffer.address, chunk)
36
+ ram.poke(buffer.count, chunk.length)
37
+ @pending_keys = nil if @pending_keys.empty?
38
+ end
39
+
40
+ def ascii_to_petscii(byte)
41
+ case byte
42
+ when 0x61..0x7a then byte - 0x20
43
+ when 0x41..0x5a then byte + 0x80
44
+ else byte
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Machine
5
+ # Brings the machine to the point a snapshot file is saved at: the main
6
+ # CPU between instructions, or jammed, with no REU transfer holding the
7
+ # bus. C64, Vic20, C128 and Plus4 each include it, and answer
8
+ # #settled_for_snapshot? and #settle_chips_for_snapshot, which brings
9
+ # what each chip defers up to the cycle without running one.
10
+ module Settling
11
+ # The cycles settling may run: the longest instruction stretched by
12
+ # the VIC's longest hold on the bus, and an REU swap of 64K, two
13
+ # cycles a byte, stretched the same way.
14
+ LIMIT = 100 + 0x40000
15
+
16
+ # Runs the machine on, through its own #cycle!, to the settle point,
17
+ # and returns the cycles that took. A settled machine runs none.
18
+ def settle_for_snapshot!
19
+ ran = 0
20
+ while ran < LIMIT && !settled_for_snapshot?
21
+ cycle!
22
+ ran += 1
23
+ end
24
+ settle_chips_for_snapshot
25
+ ran
26
+ end
27
+ end
28
+ end
29
+ end