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
@@ -132,3 +132,41 @@ c128/character.rom
132
132
  The character generator ROM, 390059-01 (characters.390059-01.bin),
133
133
  8 KB: the C64's set, the same as character.rom, followed by the C128's.
134
134
  SHA-1 29ed066d513f2d5c09ff26d9166ba23c2afb2b3f.
135
+
136
+ Commodore 264 series ROMs
137
+ =========================
138
+
139
+ Downloaded from
140
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/plus4/
141
+ for the Plus/4, the C16 and the C116, which share BASIC and the KERNAL.
142
+ Nothing maps them yet.
143
+
144
+ plus4/basic.rom
145
+ ---------------
146
+ BASIC 3.5, 318006-01 (basic.318006-01.bin), for $8000-$BFFF in ROM
147
+ bank 0.
148
+ SHA-1 161c96b4ad20f3a4f2321808e37a5ded26a135dd.
149
+
150
+ plus4/kernal-pal.rom
151
+ --------------------
152
+ The PAL KERNAL, 318004-05 (kernal.318004-05.bin), for $C000-$FFFF in
153
+ ROM bank 0, with the character set from $D000.
154
+ SHA-1 7c7e07f016391174a557e790c4ef1cbe33512cdb.
155
+
156
+ plus4/kernal-ntsc.rom
157
+ ---------------------
158
+ The NTSC KERNAL, 318005-05 (kernal.318005-05.bin), for $C000-$FFFF in
159
+ ROM bank 0.
160
+ SHA-1 a3d9e5be091b98de39a046ab167fb7632d053682.
161
+
162
+ plus4/function-low.rom
163
+ ----------------------
164
+ The Plus/4's 3-plus-1 function ROM, low half, 317053-01
165
+ (3-plus-1.317053-01.bin), for $8000-$BFFF in ROM bank 1.
166
+ SHA-1 3b69f6e7cb4c18bb08e203fb18b7dabfa853390f.
167
+
168
+ plus4/function-high.rom
169
+ -----------------------
170
+ The Plus/4's 3-plus-1 function ROM, high half, 317054-01
171
+ (3-plus-1.317054-01.bin), for $C000-$FFFF in ROM bank 1.
172
+ SHA-1 0ad7ac2db7da692d972e586ca0dfd747d82c7693.
Binary file
@@ -7,31 +7,20 @@ module Badline
7
7
  C128Setup = Data.define(:model, :sid_model, :mode)
8
8
 
9
9
  class C128Setup
10
- MARKER = "C128"
11
10
  SID_MODELS = %i[mos6581 mos8580].freeze
12
11
  MODES = %i[c64 c128].freeze
13
12
 
14
- # The setup a C128's State starts with.
15
- def self.from(state)
16
- input = StateReader.new(state)
17
- input.marker(MARKER)
18
- input.check_stamp
19
- read(input)
13
+ # The model, the SID and the mode by name, in a keyed snapshot module.
14
+ def write_fields(out)
15
+ out.text("model", model).text("sid_model", sid_model.to_s).text("mode", mode.to_s)
20
16
  end
21
17
 
22
- def self.read(input)
23
- model = input.string
24
- known = C128::Model::ALL.any? { |candidate| candidate.name == model }
25
- raise FormatError, "the state names a C128 model badline doesn't know" unless known
26
-
27
- sid_model = SID_MODELS.fetch(input.int)
28
- new(model:, sid_model:, mode: MODES.fetch(input.int))
29
- rescue IndexError
30
- raise FormatError, "the state names a SID or a mode badline doesn't know"
31
- end
32
-
33
- def write(out)
34
- out.string(model).int(SID_MODELS.index(sid_model)).int(MODES.index(mode))
18
+ def self.read_fields(input)
19
+ model = input.choice("model", C128::Model::ALL.map(&:name), "c128")
20
+ sid = input.choice("sid_model", SID_MODELS.map(&:to_s), "mos6581")
21
+ mode = input.choice("mode", MODES.map(&:to_s), "c64")
22
+ new(model:, sid_model: SID_MODELS.find { |name| name.to_s == sid } || :mos6581,
23
+ mode: MODES.find { |name| name.to_s == mode } || :c64)
35
24
  end
36
25
 
37
26
  # A machine built this way, at power-on.
@@ -9,7 +9,6 @@ module Badline
9
9
  :board)
10
10
 
11
11
  class C64Setup
12
- MARKER = "COMPUTER"
13
12
  SID_MODELS = %i[mos6581 mos8580].freeze
14
13
  REGIONS = [Region::PAL, Region::NTSC, Region::NTSC_OLD, Region::DREAN].freeze
15
14
  RAM_EXPANSIONS = [:none, *C64::BankedRAM::TYPES.keys].freeze
@@ -22,23 +21,6 @@ module Badline
22
21
  kernal: address_bus.kernal, datasette: address_bus.datasette.connected?, board: address_bus.board)
23
22
  end
24
23
 
25
- # The setup a C64's State starts with.
26
- def self.from(state)
27
- input = StateReader.new(state)
28
- input.marker(MARKER)
29
- input.check_stamp
30
- read(input)
31
- end
32
-
33
- def self.read(input)
34
- new(vic_model: Chips::VIC::MODELS.fetch(input.int), cia_model: Chips::CIA::MODELS.fetch(input.int),
35
- sid_model: SID_MODELS.fetch(input.int), region: REGIONS.fetch(input.int),
36
- ram_expansion: ram_expansion(RAM_EXPANSIONS.fetch(input.int)), reu: reu(input.int),
37
- kernal: KERNALS.fetch(input.int), datasette: input.boolean?, board: BOARDS.fetch(input.int))
38
- rescue IndexError
39
- raise FormatError, "the state names a chip model, region, RAM expansion, KERNAL or board badline doesn't know"
40
- end
41
-
42
24
  def self.ram_expansion(name) = name == :none ? nil : name
43
25
 
44
26
  # An REU's size in K, written as 0 for none.
@@ -46,14 +28,36 @@ module Badline
46
28
  return nil if size_kb.zero?
47
29
  return size_kb if C64::REU::SIZES_KB.include?(size_kb)
48
30
 
49
- raise FormatError, "the state names a #{size_kb}K REU, which badline doesn't build"
31
+ raise FormatError, "the snapshot names a #{size_kb}K REU, which badline doesn't build"
32
+ end
33
+
34
+ # The setup in a keyed snapshot module (Snapshot::Keyed), with each
35
+ # model, the region, the RAM expansion, the KERNAL and the board by
36
+ # name.
37
+ def write_fields(out)
38
+ out.text("vic_model", vic_model.to_s).text("cia_model", cia_model.to_s).text("sid_model", sid_model.to_s)
39
+ out.text("region", region.name.to_s).text("ram_expansion", (ram_expansion || :none).to_s)
40
+ out.int("reu_kb", reu || 0).text("kernal", kernal.to_s).bool("datasette", datasette)
41
+ out.text("board", board.to_s)
42
+ end
43
+
44
+ # The setup a keyed snapshot module names, a C64's at power-on for
45
+ # anything it leaves out or names that badline doesn't build.
46
+ def self.read_fields(input)
47
+ region = named(input, "region", REGIONS.map(&:name), :pal)
48
+ new(vic_model: named(input, "vic_model", Chips::VIC::MODELS, :mos6569),
49
+ cia_model: named(input, "cia_model", Chips::CIA::MODELS, :mos6526),
50
+ sid_model: named(input, "sid_model", SID_MODELS, :mos6581),
51
+ region: REGIONS.find { |each| each.name == region } || Region::PAL,
52
+ ram_expansion: ram_expansion(named(input, "ram_expansion", RAM_EXPANSIONS, :none)),
53
+ reu: reu(input.int("reu_kb", 0)), kernal: named(input, "kernal", KERNALS, :c64),
54
+ datasette: input.bool("datasette", true), board: named(input, "board", BOARDS, :c64))
50
55
  end
51
56
 
52
- def write(out)
53
- out.int(Chips::VIC::MODELS.index(vic_model)).int(Chips::CIA::MODELS.index(cia_model))
54
- out.int(SID_MODELS.index(sid_model)).int(REGIONS.index(region))
55
- out.int(RAM_EXPANSIONS.index(ram_expansion || :none)).int(reu || 0)
56
- out.int(KERNALS.index(kernal)).boolean(datasette).int(BOARDS.index(board))
57
+ # The Symbol of `names` the text at `key` names.
58
+ def self.named(input, key, names, default)
59
+ text = input.choice(key, names.map(&:to_s), default.to_s)
60
+ names.find { |name| name.to_s == text } || default
57
61
  end
58
62
 
59
63
  # A machine built this way, at power-on.
@@ -4,6 +4,10 @@ require "zlib"
4
4
 
5
5
  module Badline
6
6
  module Snapshot
7
+ # A file that isn't a snapshot badline reads, is cut short, or is of a
8
+ # machine built another way.
9
+ class FormatError < StandardError; end
10
+
7
11
  # One module of a snapshot: its name, its version and its payload,
8
12
  # without the 22-byte module header.
9
13
  Section = Data.define(:name, :major, :minor, :data) do
@@ -30,6 +34,8 @@ module Badline
30
34
  VIC20 = "VIC20"
31
35
  # x128 names its machine C128.
32
36
  C128 = "C128"
37
+ # xplus4 names its machine PLUS4, for the C16 too.
38
+ PLUS4 = "PLUS4"
33
39
  GZIP_MAGIC = "\x1f\x8b".b
34
40
 
35
41
  # The VICE version block, when VICE wrote the file: four version
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module FieldDoc
6
+ # The doc's markdown: one table per module, of each key, its type and
7
+ # its default, with keys that differ only in a unit's number, such as
8
+ # voice1 to voice3, on one row.
9
+ class Markdown
10
+ NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4" }.freeze
11
+ TAG_TYPES = { Keyed::INT => "int", Keyed::BOOL => "bool", Keyed::BYTES => "bytes", Keyed::INTS => "ints",
12
+ Keyed::TEXT => "text" }.freeze
13
+
14
+ # A row: the key, the type, and the defaults each family whose
15
+ # machines read or write it reads it with.
16
+ Row = Struct.new(:key, :type, :defaults)
17
+
18
+ INTRO = <<~MARKDOWN
19
+ # Snapshot format
20
+
21
+ This file is generated by `rake snapshot:fields` from the code that
22
+ loads a snapshot. Don't edit it by hand. doc/snapshots.md covers
23
+ what a snapshot holds and which versions load.
24
+
25
+ A snapshot is a VICE `.vsf` container. The machine is in modules
26
+ named `BL-` plus a unit, each a deflated list of records: a key, a
27
+ type and a value. The types are `int` (a zigzag BER integer), `bool`,
28
+ `bytes`, `ints` (a list of integers) and `text`. A load skips a key
29
+ it doesn't know, and a key that isn't there leaves the value a
30
+ machine built afresh has: the default below. `word`, `byte` and
31
+ `int & 0x..` are ints cut to the latch's width. A text that lists
32
+ its values takes one of them. An optional int is absent when the
33
+ latch holds nothing. A true drive's modules are named from its
34
+ device number, `BL-D8` to `BL-D11`, and the SIDs past the first
35
+ are `BL-SID2` onwards, in the order they were fitted. Keys with
36
+ `<N>` stand for each numbered unit. A default of "written only" is
37
+ a key the load doesn't read, or reads only when the snapshot has
38
+ it. Where the machines differ, the default is given for each, and a
39
+ key only some of them have names them.
40
+ MARKDOWN
41
+
42
+ def initialize(loads)
43
+ @modules = {}
44
+ loads.each { |load| add(load) }
45
+ end
46
+
47
+ def to_s
48
+ sections = @modules.map do |name, rows|
49
+ families = rows.values.flat_map { |row| row.defaults.keys }.uniq
50
+ ["## #{name}", "", "Written by the #{families.map { |family| NAMES.fetch(family) }.join(', ')}.", "",
51
+ "| Key | Type | Default |", "| --- | --- | --- |",
52
+ *rows.values.map { |row| line(row, families) }].join("\n")
53
+ end
54
+ "#{INTRO}\n#{sections.join("\n\n")}\n"
55
+ end
56
+
57
+ private
58
+
59
+ def add(load)
60
+ load.fields.each do |name, fields|
61
+ numbered = numbered_keys(load.written[name].keys + fields.map(&:key))
62
+ fields.each { |field| note(name, numbered.fetch(field.key, field.key), field.type, load.family, field) }
63
+ read = fields.to_set(&:key)
64
+ load.written[name].each do |key, tag|
65
+ next if read.include?(key)
66
+
67
+ note(name, numbered.fetch(key, key), TAG_TYPES.fetch(tag, "?"), load.family, nil)
68
+ end
69
+ end
70
+ end
71
+
72
+ def note(name, key, type, family, field)
73
+ row = (@modules[name] ||= {})[key] ||= Row.new(key, type, {})
74
+ (row.defaults[family] ||= []) << (field ? brief(field.default, field.type) : "written only")
75
+ end
76
+
77
+ # Keys that differ only in a number, as the same key with <N>: each
78
+ # key whose number another key in the module shares the rest of.
79
+ def numbered_keys(keys)
80
+ groups = keys.uniq.group_by { |key| key.gsub(/(?<=[a-z])\d+(?=\.|\z)/, "<N>") }
81
+ groups.each_with_object({}) do |(general, members), numbered|
82
+ members.each { |key| numbered[key] = general } if members.length > 1
83
+ end
84
+ end
85
+
86
+ def line(row, families)
87
+ each = row.defaults.transform_values { |defaults| summary(defaults.uniq) }
88
+ default = if each.values.uniq.length == 1 && each.length == families.length then each.values.first
89
+ else each.map { |family, value| "#{NAMES.fetch(family)}: #{value}" }.join("; ")
90
+ end
91
+ "| `#{row.key}` | #{row.type} | #{default} |"
92
+ end
93
+
94
+ def summary(defaults) = defaults.length > 3 ? "varies" : defaults.join(" or ")
95
+
96
+ def brief(value, type)
97
+ case value
98
+ when nil then "absent"
99
+ when Array then list(value)
100
+ when String then type.start_with?("bytes") ? list(value.bytes) : value.inspect
101
+ else value.to_s
102
+ end
103
+ end
104
+
105
+ def list(values)
106
+ return values.inspect if values.length <= 8
107
+
108
+ values.uniq.length == 1 ? "#{values.length} × #{values.first}" : "#{values.length} values"
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline"
4
+ require "badline/c128"
5
+ require "badline/plus4"
6
+
7
+ module Badline
8
+ module Snapshot
9
+ # doc/snapshot-format.md, written by running the code: each family's
10
+ # machine is saved with its attachments, and its modules loaded into a
11
+ # machine built the same way at power-on through Keyed::Recorders,
12
+ # which note every key read with its type and the value the new machine
13
+ # had, its default. `rake snapshot:fields` writes it, and a spec checks
14
+ # that the file matches.
15
+ module FieldDoc
16
+ PATH = File.expand_path("../../../doc/snapshot-format.md", __dir__)
17
+
18
+ # Modules read through Recorders.
19
+ class RecordingModules < Keyed::Modules
20
+ private
21
+
22
+ def parse(section) = Keyed::Recorder.parse(section)
23
+
24
+ def missing(name) = Keyed::Recorder.missing(name)
25
+ end
26
+
27
+ # One machine saved and loaded: the family it belongs to, and for
28
+ # each module the keys its records hold, with their tags, and the fields the load read.
29
+ Load = Data.define(:family, :written, :fields)
30
+
31
+ # The cartridges by CRT hardware type: EasyFlash, then a mapper of
32
+ # each kind of register: Ocean's bank, Comal-80's bank for both
33
+ # windows, Zaxxon's ROMH bank, and the Action Replay's and the
34
+ # Pagefox's control registers.
35
+ CARTRIDGES = [32, 5, 21, 18, 1, 53].freeze
36
+
37
+ # The machines the doc is made from: each family at power-on, the C64
38
+ # with each kind of expansion, cartridge and media, and a second SID,
39
+ # and the 264 series' models, its TCBM interface, cartridge, tape and
40
+ # true drive.
41
+ def self.machines
42
+ {
43
+ "c64" => [C64.new, C64.new(ram_expansion: :plus256k), C64.new(reu: 512),
44
+ *CARTRIDGES.map { |type| with_cartridge(type) }, with_trap_dos, with_tape, with_drive(:d64),
45
+ with_drive(:d81), with_extra_sids],
46
+ "vic20" => [Vic20.new, with_rom],
47
+ "c128" => [C128.new(mode: :c128), with_drive(:d71)],
48
+ "plus4" => [Plus4.new, Plus4.new(model: "c16"), Plus4.new(tcbm: true), plus4_with_cartridge,
49
+ plus4_with_tape, plus4_with_drive]
50
+ }
51
+ end
52
+
53
+ def self.blank(length) = Array.new(length, 0)
54
+
55
+ def self.with_cartridge(type)
56
+ chips = [0x8000, 0xa000].map do |address|
57
+ Storage::CRTFile::Chip.new(chip_type: 2, bank: 0, address:, data: blank(0x2000))
58
+ end
59
+ image = Storage::CRTFile::Image.new(hardware_type: type, subtype: 0, exrom: 1, game: 0, name: "DOC", chips:)
60
+ C64.new.tap do |machine|
61
+ machine.attach_cartridge(C64::Cartridge.from_crt(Storage::CRTFile.new(bytes: Storage::CRTFile.encode(image))))
62
+ end
63
+ end
64
+
65
+ # The SID player's machine for a tune on two SIDs.
66
+ def self.with_extra_sids
67
+ C64.new.tap { |machine| machine.address_bus.add_sid(Chips::SID.new(at: 0xd420)) }
68
+ end
69
+
70
+ def self.with_trap_dos
71
+ C64.new.tap { |machine| machine.mount(Storage::D64Image.new("", read_only: true, bytes: blank(174_848))) }
72
+ end
73
+
74
+ def self.with_tape
75
+ bytes = "C64-TAPE-RAW".bytes + [1, 0, 0, 0, 4, 0, 0, 0, 0x30, 0x30, 0x30, 0x30]
76
+ C64.new.tap { |machine| machine.datasette.insert(Storage::TAP.new("", bytes:)) }
77
+ end
78
+
79
+ def self.with_drive(kind)
80
+ case kind
81
+ when :d81
82
+ C64.new.tap do |machine|
83
+ machine.plug_drive1581.insert(Drives::Model1581::Disk.new(Storage::D81Image.new("", bytes: blank(819_200))))
84
+ end
85
+ when :d71
86
+ C128.new(mode: :c128).tap do |machine|
87
+ image = Storage::D71Image.new("", bytes: blank(349_696))
88
+ machine.plug_true_drive.insert(Drives::Model1541::Disk.decode(image))
89
+ end
90
+ else
91
+ C64.new.tap do |machine|
92
+ image = Storage::D64Image.new("", bytes: blank(174_848))
93
+ machine.plug_true_drive.insert(Drives::Model1541::Disk.decode(image))
94
+ end
95
+ end
96
+ end
97
+
98
+ # A Plus/4 with a cartridge in each of cartridge 1's slots.
99
+ def self.plus4_with_cartridge
100
+ chips = [0x8000, 0xc000].map do |address|
101
+ Storage::CRTFile::Chip.new(chip_type: 0, bank: 0, address:, data: blank(0x4000))
102
+ end
103
+ Plus4.new.tap { |machine| machine.attach_cartridge(chips) }
104
+ end
105
+
106
+ # A C16 with a version 2 tape in its datasette, which times the read
107
+ # line's level.
108
+ def self.plus4_with_tape
109
+ bytes = "C16-TAPE-RAW".bytes + [2, 2, 0, 0, 4, 0, 0, 0, 0x30, 0x30, 0x30, 0x30]
110
+ Plus4.new(model: "c16").tap { |machine| machine.datasette.insert(Storage::TAP.new("", bytes:)) }
111
+ end
112
+
113
+ def self.plus4_with_drive
114
+ Plus4.new.tap do |machine|
115
+ image = Storage::D64Image.new("", bytes: blank(174_848))
116
+ machine.plug_true_drive.insert(Drives::Model1541::Disk.decode(image))
117
+ end
118
+ end
119
+
120
+ def self.with_rom
121
+ Vic20.new.tap { |machine| machine.bus.map_rom(0xa000, blank(0x2000)) }
122
+ end
123
+
124
+ # Saves `machine` and loads it into a new one through Recorders.
125
+ def self.load(family, machine)
126
+ modules = RecordingModules.new(Image.of(machine).sections)
127
+ Image.setup(modules.machine).build.load_modules(modules)
128
+ modules.machine.text("written_by", "")
129
+ written = {}
130
+ fields = {}
131
+ modules.readers.each do |reader|
132
+ written[reader.name] = reader.tags
133
+ fields[reader.name] = reader.fields
134
+ end
135
+ Load.new(family:, written:, fields:)
136
+ end
137
+
138
+ def self.loads = machines.flat_map { |family, list| list.map { |machine| load(family, machine) } }
139
+
140
+ # The doc, as markdown.
141
+ def self.markdown(loads = self.loads) = Markdown.new(loads).to_s
142
+
143
+ def self.write(path = PATH) = File.write(path, markdown)
144
+ end
145
+ end
146
+ end
147
+
148
+ require "badline/snapshot/field_doc/markdown"
@@ -2,37 +2,115 @@
2
2
 
3
3
  module Badline
4
4
  module Snapshot
5
- # What a restore applied and what it left out: module names, and a line
6
- # for each thing left out, saying why.
7
- Report = Data.define(:applied, :ignored)
5
+ # What a restore applied and what it left out: module names and a line
6
+ # for each thing left out or changed, saying why. For a badline
7
+ # snapshot, `modules` holds each BL-* module's Keyed::Report, which
8
+ # give the fields per module this badline doesn't know, the modules it
9
+ # expected and didn't find, and the values it read other than as
10
+ # written, and `written_by` the badline version that saved it.
11
+ class Report
12
+ attr_reader :applied, :ignored, :written_by
13
+
14
+ def initialize(applied:, ignored:, modules: [], written_by: "")
15
+ @applied = applied
16
+ @ignored = ignored
17
+ @modules = modules
18
+ @written_by = written_by
19
+ end
20
+
21
+ # The keys nothing read, by module, for the modules that had any.
22
+ def unknown
23
+ @modules.reject { |report| report.unknown.empty? }.to_h { |report| [report.name, report.unknown] }
24
+ end
25
+
26
+ # The modules the snapshot didn't have.
27
+ def missing = @modules.select(&:missing).map(&:name)
8
28
 
9
- # A snapshot read from a file.
29
+ # A line for each value read other than as written.
30
+ def adjusted = @modules.flat_map { |report| report.adjusted.map { |line| "#{report.name}: #{line}" } }
31
+ end
32
+
33
+ # A snapshot: the container and its modules, read from a file or taken
34
+ # of a machine in memory (.of).
10
35
  class Image
36
+ MACHINE = "BL-MACHINE"
37
+ FAMILIES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4" }.freeze
38
+
11
39
  attr_reader :container
12
40
 
13
- def initialize(container)
41
+ # `name` is what a refusal calls the snapshot, such as its file's name.
42
+ def initialize(container, name: "the snapshot")
14
43
  @container = container
44
+ @name = name
45
+ end
46
+
47
+ # The machine's snapshot, saved as a file saves it: the machine runs
48
+ # on to the end of the instruction its CPU is in, and of an REU
49
+ # transfer under way (Machine::Settling), then writes its BL-*
50
+ # modules.
51
+ def self.of(computer)
52
+ computer.settle_for_snapshot!
53
+ new(Container.new(computer.save_modules, machine: machine_name(computer)))
54
+ end
55
+
56
+ # The snapshot with VICE's modules ahead of the BL-* ones for a C64,
57
+ # so x64sc opens it too. Raises FormatError for a Plus/4, whose
58
+ # snapshots badline doesn't write for xplus4.
59
+ def self.exported(computer)
60
+ raise FormatError, "badline doesn't export a Plus/4's snapshot for VICE" if computer.family == :plus4
61
+
62
+ computer.settle_for_snapshot!
63
+ sections = computer.save_modules
64
+ sections = Vice.export(computer) + sections if computer.family == :c64
65
+ new(Container.new(sections, machine: machine_name(computer)))
66
+ end
67
+
68
+ # The machine as VICE's emulators name it.
69
+ def self.machine_name(computer)
70
+ case computer.family
71
+ when :vic20 then Container::VIC20
72
+ when :c128 then Container::C128
73
+ when :plus4 then Container::PLUS4
74
+ else Container::MACHINE
75
+ end
76
+ end
77
+
78
+ # How the machine was built, from the family BL-MACHINE, read by
79
+ # `machine`, names.
80
+ def self.setup(machine)
81
+ case machine.choice("family", FAMILIES.keys, "c64")
82
+ when "vic20" then Vic20Setup.read_fields(machine)
83
+ when "c128" then C128Setup.read_fields(machine)
84
+ when "plus4" then Plus4Setup.read_fields(machine)
85
+ else C64Setup.read_fields(machine)
86
+ end
15
87
  end
16
88
 
17
89
  def sections = @container.sections
18
90
 
19
- # Whether badline wrote it, with the whole machine in a BADLINE module.
20
- def badline? = !section.nil?
91
+ def to_s = @container.to_s
21
92
 
22
- # The State the BADLINE module holds.
23
- def state = MachineState.state(section)
93
+ def ==(other) = other.is_a?(Image) && to_s == other.to_s
94
+
95
+ # Whether badline wrote it, with the whole machine in BL-* modules.
96
+ def badline? = !@container[MACHINE].nil?
24
97
 
25
98
  # A new machine, built as the snapshot's was and restored from it. A
26
- # badline snapshot restores from its BADLINE module alone, which
27
- # carries everything its VICE modules do, and a VICE one through the
28
- # modules badline reads. xvic's and x128's own modules aren't read.
29
- # Yields a line for each thing left out, or warns it without a block.
30
- def load(&)
99
+ # badline snapshot restores from its BL-* modules alone, and a VICE
100
+ # one through the modules badline reads. xvic's, x128's and xplus4's own
101
+ # modules aren't read. Yields a line for each thing left out, or
102
+ # warns it without a block. `detached` restores it without the host's
103
+ # files (Keyed::Modules).
104
+ def load(detached: false, &)
105
+ refuse_old_layout
31
106
  raise FormatError, "badline reads only its own VIC-20 snapshots, not xvic's" if vic20? && !badline?
32
107
  raise FormatError, "badline reads only its own C128 snapshots, not x128's" if c128? && !badline?
108
+ raise FormatError, "badline reads only its own Plus/4 snapshots, not xplus4's" if plus4? && !badline?
109
+ return load_vice(&) unless badline?
33
110
 
34
- computer = badline? ? opening { Setup.restored(state) } : Vice.setup(@container).build
35
- tell(badline? ? state_report(computer) : Vice::Restore.apply(@container, computer), &)
111
+ modules = Keyed::Modules.new(sections, detached:)
112
+ computer = opening { Image.setup(modules.machine).build.load_modules(modules) }
113
+ tell(report(modules, computer), &)
36
114
  computer
37
115
  end
38
116
 
@@ -42,25 +120,58 @@ module Badline
42
120
  # Whether the machine is a C128, as the container names it.
43
121
  def c128? = @container.machine == Container::C128
44
122
 
45
- # Restores `computer` from the snapshot, and returns the Report. A
46
- # snapshot that fails leaves the machine as it was.
123
+ # Whether the machine is of the 264 series, as the container names it.
124
+ def plus4? = @container.machine == Container::PLUS4
125
+
126
+ # Restores `computer` from the snapshot, and returns the Report. The
127
+ # snapshot goes into a new machine first, so one that fails, or is of
128
+ # a machine built another way, leaves `computer` as it was.
47
129
  def restore(computer, &)
48
- report = badline? ? restore_state(computer) : Vice::Restore.import(@container, computer)
130
+ refuse_old_layout
131
+ report = badline? ? restore_modules(computer) : Vice::Restore.import(@container, computer)
49
132
  tell(report, &)
50
133
  report
51
134
  end
52
135
 
53
136
  private
54
137
 
55
- def section = @container[MachineState::NAME]
138
+ def load_vice(&)
139
+ computer = Vice.setup(@container).build
140
+ tell(Vice::Restore.apply(@container, computer), &)
141
+ computer
142
+ end
56
143
 
57
- def tell(report)
58
- report.ignored.each { |line| block_given? ? yield(line) : warn("badline: #{line}") }
144
+ # The snapshot goes into a new machine first, so one that fails
145
+ # leaves `computer` as it was.
146
+ def restore_modules(computer)
147
+ modules = Keyed::Modules.new(sections)
148
+ family = modules.machine.choice("family", FAMILIES.keys, "c64")
149
+ unless family == computer.family.to_s
150
+ raise FormatError, "the snapshot is of a #{FAMILIES.fetch(family, family)}, " \
151
+ "not a #{FAMILIES.fetch(computer.family.to_s)}"
152
+ end
153
+
154
+ trial = Keyed::Modules.new(sections)
155
+ opening { Image.setup(trial.machine).build.load_modules(trial) }
156
+ opening { computer.load_modules(modules) }
157
+ report(modules, computer)
59
158
  end
60
159
 
61
- def restore_state(computer)
62
- opening { computer.restore(state) }
63
- state_report(computer)
160
+ # A snapshot from a badline before the keyed modules, which only the
161
+ # version that saved it reads.
162
+ def refuse_old_layout
163
+ old = @container[MachineState::NAME]
164
+ return if old.nil? || badline?
165
+
166
+ version = MachineState.written_by(old)
167
+ layout = "in the old snapshot layout, which this badline can't read."
168
+ raise FormatError, "#{@name} was saved by an earlier badline #{layout}" if version.empty?
169
+
170
+ raise FormatError, "#{@name} was saved by badline #{version} #{layout} It still opens in badline #{version}."
171
+ end
172
+
173
+ def tell(report)
174
+ report.ignored.each { |line| block_given? ? yield(line) : warn("badline: #{line}") }
64
175
  end
65
176
 
66
177
  def opening
@@ -69,10 +180,14 @@ module Badline
69
180
  raise FormatError, "a file the snapshot names won't open: #{e.message}"
70
181
  end
71
182
 
72
- def state_report(computer)
183
+ def report(modules, computer)
184
+ written_by = modules.machine.text("written_by", "")
185
+ reports = modules.readers.map(&:report)
186
+ ignored = reports.flat_map(&:lines)
187
+ ignored.concat(modules.unread.map { |name| "#{name}: badline doesn't read this module, left out" })
73
188
  lost = computer.init_handlers_lost
74
- ignored = lost.positive? ? ["#{lost} on_init handler(s) the saved machine had yet to run, left out"] : []
75
- Report.new(applied: [MachineState::NAME], ignored:)
189
+ ignored << "#{lost} on_init handler(s) the saved machine had yet to run, left out" if lost.positive?
190
+ Report.new(applied: reports.reject(&:missing).map(&:name), ignored:, modules: reports, written_by:)
76
191
  end
77
192
  end
78
193
  end