badline 0.10.1 → 0.11.1

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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/CLAUDE.md +25 -6
  4. data/CONTRIBUTING.md +8 -3
  5. data/README.md +63 -4
  6. data/doc/pinned-behaviour.md +75 -0
  7. data/doc/snapshots.md +15 -0
  8. data/lib/badline/address_bus/pla.rb +3 -16
  9. data/lib/badline/address_bus.rb +14 -0
  10. data/lib/badline/c128/banks.rb +35 -0
  11. data/lib/badline/c128/bus.rb +347 -0
  12. data/lib/badline/c128/bus_state.rb +53 -0
  13. data/lib/badline/c128/color_lines.rb +39 -0
  14. data/lib/badline/c128/cpu.rb +10 -0
  15. data/lib/badline/c128/drives.rb +95 -0
  16. data/lib/badline/c128/fast_serial.rb +62 -0
  17. data/lib/badline/c128/keys.rb +35 -0
  18. data/lib/badline/c128/mmu.rb +197 -0
  19. data/lib/badline/c128/mmu_pages.rb +105 -0
  20. data/lib/badline/c128/model.rb +39 -0
  21. data/lib/badline/c128/modes.rb +48 -0
  22. data/lib/badline/c128/saved_state.rb +121 -0
  23. data/lib/badline/c128/vdc/memory.rb +54 -0
  24. data/lib/badline/c128/vdc/painter.rb +143 -0
  25. data/lib/badline/c128/vdc/raster.rb +97 -0
  26. data/lib/badline/c128/vdc/window.rb +215 -0
  27. data/lib/badline/c128/vdc.rb +268 -0
  28. data/lib/badline/c128.rb +322 -0
  29. data/lib/badline/chrout_trap.rb +3 -0
  30. data/lib/badline/cia/fast_forward.rb +42 -0
  31. data/lib/badline/cia/serial.rb +12 -0
  32. data/lib/badline/cia/timer.rb +39 -0
  33. data/lib/badline/cia.rb +2 -0
  34. data/lib/badline/computer/attachments.rb +10 -13
  35. data/lib/badline/computer/kernal_traps.rb +29 -0
  36. data/lib/badline/computer.rb +5 -0
  37. data/lib/badline/drive/core.rb +172 -0
  38. data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
  39. data/lib/badline/drive/idle_cpu.rb +28 -0
  40. data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
  41. data/lib/badline/drive/serial_lines.rb +73 -0
  42. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
  43. data/lib/badline/drive/watch.rb +150 -0
  44. data/lib/badline/drive.rb +9 -0
  45. data/lib/badline/drive1541/bus.rb +5 -129
  46. data/lib/badline/drive1541/byte_ready.rb +48 -0
  47. data/lib/badline/drive1541/cpu.rb +1 -19
  48. data/lib/badline/drive1541/disk.rb +77 -20
  49. data/lib/badline/drive1541/disk_state.rb +7 -2
  50. data/lib/badline/drive1541/disk_via.rb +3 -0
  51. data/lib/badline/drive1541/mechanism.rb +48 -14
  52. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  53. data/lib/badline/drive1541/rotation.rb +5 -5
  54. data/lib/badline/drive1541/serial_port.rb +4 -60
  55. data/lib/badline/drive1541.rb +13 -172
  56. data/lib/badline/drive1571/bus.rb +111 -0
  57. data/lib/badline/drive1571/cpu.rb +11 -0
  58. data/lib/badline/drive1571/serial_port.rb +37 -0
  59. data/lib/badline/drive1571/serial_via.rb +25 -0
  60. data/lib/badline/drive1571/wd1770.rb +34 -0
  61. data/lib/badline/drive1571.rb +225 -0
  62. data/lib/badline/frontend/app.rb +35 -22
  63. data/lib/badline/frontend/boot.rb +31 -1
  64. data/lib/badline/frontend/controls.rb +11 -0
  65. data/lib/badline/frontend/drive_led.rb +1 -1
  66. data/lib/badline/frontend/keys.rb +28 -1
  67. data/lib/badline/frontend/machine_window.rb +16 -13
  68. data/lib/badline/frontend/menu_media.rb +3 -3
  69. data/lib/badline/frontend/menu_pages.rb +8 -2
  70. data/lib/badline/frontend/menu_ports.rb +1 -1
  71. data/lib/badline/frontend/pause_menu.rb +1 -0
  72. data/lib/badline/frontend/screen.rb +33 -8
  73. data/lib/badline/frontend/screens.rb +90 -0
  74. data/lib/badline/frontend/timeline.rb +15 -4
  75. data/lib/badline/frontend.rb +1 -0
  76. data/lib/badline/iec_bus.rb +54 -4
  77. data/lib/badline/kernal_trap/file.rb +1 -1
  78. data/lib/badline/kernal_trap/layout.rb +58 -8
  79. data/lib/badline/kernal_trap/load.rb +1 -1
  80. data/lib/badline/kernal_trap/save.rb +1 -1
  81. data/lib/badline/kernal_trap/serial.rb +5 -0
  82. data/lib/badline/machine.rb +20 -9
  83. data/lib/badline/media/true_drive.rb +26 -23
  84. data/lib/badline/media.rb +8 -6
  85. data/lib/badline/options/event.rb +5 -1
  86. data/lib/badline/options/family.rb +52 -0
  87. data/lib/badline/options/help.rb +7 -2
  88. data/lib/badline/options/table.rb +8 -2
  89. data/lib/badline/options/validation.rb +14 -1
  90. data/lib/badline/options.rb +20 -19
  91. data/lib/badline/roms/README +36 -0
  92. data/lib/badline/roms/c128/basic.rom +0 -0
  93. data/lib/badline/roms/c128/character.rom +0 -0
  94. data/lib/badline/roms/c128/kernal.rom +0 -0
  95. data/lib/badline/roms/dos1571.rom +0 -0
  96. data/lib/badline/snapshot/container.rb +2 -0
  97. data/lib/badline/snapshot/image.rb +14 -0
  98. data/lib/badline/snapshot.rb +9 -2
  99. data/lib/badline/storage/d64_image.rb +3 -0
  100. data/lib/badline/storage/d71_image.rb +9 -4
  101. data/lib/badline/storage/g64_image.rb +7 -2
  102. data/lib/badline/time_of_day.rb +9 -0
  103. data/lib/badline/version.rb +1 -1
  104. data/lib/badline/via/fast_forward.rb +3 -3
  105. data/lib/badline/vic/display_state.rb +12 -0
  106. data/lib/badline/vic/registers.rb +3 -0
  107. data/lib/badline/vic.rb +108 -7
  108. data/lib/badline/vic20/attachments.rb +6 -0
  109. data/lib/badline/z80/arithmetic.rb +117 -0
  110. data/lib/badline/z80/bit_page.rb +55 -0
  111. data/lib/badline/z80/block_io.rb +58 -0
  112. data/lib/badline/z80/block_transfers.rb +59 -0
  113. data/lib/badline/z80/bus_cycles.rb +111 -0
  114. data/lib/badline/z80/core.rb +111 -0
  115. data/lib/badline/z80/exchanges.rb +50 -0
  116. data/lib/badline/z80/extended_page.rb +89 -0
  117. data/lib/badline/z80/flags.rb +37 -0
  118. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  119. data/lib/badline/z80/jumps.rb +82 -0
  120. data/lib/badline/z80/loads.rb +98 -0
  121. data/lib/badline/z80/main_page.rb +134 -0
  122. data/lib/badline/z80/registers.rb +120 -0
  123. data/lib/badline/z80/shifts.rb +64 -0
  124. data/lib/badline/z80.rb +11 -0
  125. data/lib/badline.rb +1 -0
  126. metadata +59 -4
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class VDC
6
+ # Paints the VDC's scan lines into its display, one line at a time,
7
+ # from the registers as they stand when the line ends. Each pixel of
8
+ # the display is one period of the 16 MHz dot clock, so a pixel of
9
+ # the double-width mode, R25 bit 4, covers two. Window paints the
10
+ # characters of the lines in the displayed rows.
11
+ #
12
+ # The horizontal counter that R2, R34 and R35 compare runs
13
+ # COUNTER_LEAD characters ahead of the character on screen: the soci
14
+ # testprogs set R34 = 6 and R35 = R1 + 8 for one character of border
15
+ # either side of the display, as test01.jpg shows on a real machine.
16
+ # The display holds each line from the end of the horizontal sync, R2
17
+ # plus R3 bits 0-3 on that counter, as a monitor shows it, and its
18
+ # rows from the end of the vertical sync, which starts R7 - 1 rows
19
+ # into the frame (the Programmer's Reference Guide counts R7 "plus 1")
20
+ # and lasts R3 bits 4-7 lines, or 16 for 0. The vertical sync and the
21
+ # characters outside R34 up to R35 are black, and the rest of the
22
+ # border is the background colour, R26 bits 0-3.
23
+ #
24
+ # The display is as wide as a line and as high as a frame, up to
25
+ # MAX_WIDTH by MAX_HEIGHT; lines and pixels past those are left out.
26
+ class Painter
27
+ COUNTER_LEAD = 7
28
+ MIN_LINE_DOTS = 128
29
+ MAX_WIDTH = 1024
30
+ MAX_HEIGHT = 320
31
+
32
+ attr_reader :display, :width, :height, :dirty_lines
33
+
34
+ def initialize(registers, memory)
35
+ @registers = registers
36
+ @window = Window.new(registers, memory)
37
+ @line = Array.new(MAX_WIDTH, 0)
38
+ resize(MIN_LINE_DOTS, 1)
39
+ end
40
+
41
+ # Latches the start addresses, and starts the display again when
42
+ # the frame's size has changed.
43
+ def begin_frame(line_dots, frame_lines)
44
+ @window.latch_starts
45
+ width = line_dots.clamp(MIN_LINE_DOTS, MAX_WIDTH)
46
+ height = frame_lines.clamp(1, MAX_HEIGHT)
47
+ resize(width, height) if width != @width || height != @height
48
+ end
49
+
50
+ # The [left, top, width, height] of the display a monitor shows:
51
+ # all of a line but the horizontal sync, all of a frame but the
52
+ # vertical sync.
53
+ def crop(line_dots, frame_lines, sync_lines)
54
+ regs = @registers
55
+ shown = line_dots - ((regs[3] & 0x0f) * VDC.char_dots(regs))
56
+ [0, 0, shown.clamp(1, @width), (frame_lines - sync_lines).clamp(1, @height)]
57
+ end
58
+
59
+ # The display's size and the window's start addresses. The display
60
+ # starts again cleared, to be painted while the VDC renders.
61
+ def save_state(out)
62
+ out.int(@width).int(@height)
63
+ @window.save_state(out)
64
+ end
65
+
66
+ def load_state(input)
67
+ resize(input.int, input.int)
68
+ @window.load_state(input)
69
+ end
70
+
71
+ def clear_dirty_lines!
72
+ @dirty_lines.fill(false)
73
+ end
74
+
75
+ def clear!
76
+ @display.fill(0)
77
+ @dirty_lines.fill(true)
78
+ end
79
+
80
+ # Paints the line `line_y` lines after the vertical sync ends.
81
+ # `row` and `row_line` place it in the frame's character rows, or
82
+ # `row` is -1 in the adjust lines after the last row, and `sync` is
83
+ # set in the vertical sync. `frame` counts frames for the blinking.
84
+ def paint(line_y, row, row_line, sync, frame)
85
+ return if line_y >= @height
86
+
87
+ regs = @registers
88
+ char_dots = VDC.char_dots(regs)
89
+ line_dots = (regs[0] + 1) * char_dots
90
+ @line = Array.new(line_dots, 0) if @line.length < line_dots
91
+ line = @line
92
+ line.fill(sync ? 0 : regs[26] & 0x0f, 0, line_dots)
93
+ unless sync
94
+ @window.paint(line, row, row_line, frame) if row >= 0 && row < regs[6]
95
+ blank(line, line_dots, char_dots)
96
+ end
97
+ copy_out(line, line_y, line_dots, char_dots)
98
+ end
99
+
100
+ private
101
+
102
+ def resize(width, height)
103
+ @width = width
104
+ @height = height
105
+ @display = Array.new(width * height, 0)
106
+ @dirty_lines = Array.new(height, true)
107
+ end
108
+
109
+ def sync_end_chars(regs) = regs[2] + (regs[3] & 0x0f) - COUNTER_LEAD
110
+
111
+ def blank(line, line_dots, char_dots)
112
+ first = @registers[34]
113
+ last = @registers[35]
114
+ return if first == last
115
+
116
+ total = line_dots / char_dots
117
+ char = 0
118
+ while char < total
119
+ counter = (char + COUNTER_LEAD) % total
120
+ line.fill(0, char * char_dots, char_dots) unless enabled?(counter, first, last)
121
+ char += 1
122
+ end
123
+ end
124
+
125
+ def enabled?(counter, first, last)
126
+ return counter >= first && counter < last if first < last
127
+
128
+ counter >= first || counter < last
129
+ end
130
+
131
+ def copy_out(line, line_y, line_dots, char_dots)
132
+ origin = (sync_end_chars(@registers) * char_dots) % line_dots
133
+ base = line_y * @width
134
+ head = [line_dots - origin, @width].min
135
+ @display[base, head] = line[origin, head]
136
+ tail = [@width - head, origin].min
137
+ @display[base + head, tail] = line[0, tail] if tail.positive?
138
+ @dirty_lines[line_y] = true
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class VDC
6
+ # The VDC's raster: R0 + 1 characters a line, R9 + 1 lines a row,
7
+ # R4 + 1 rows a frame and then R5 more lines, with the frame's size
8
+ # and sync taken as it begins. Each line ends into the Painter while
9
+ # the VDC renders. Interlace, R8, is left out.
10
+ class Raster
11
+ attr_reader :frame, :frame_lines, :sync_lines
12
+
13
+ def initialize(registers, painter)
14
+ @registers = registers
15
+ @painter = painter
16
+ reset!
17
+ end
18
+
19
+ def reset!
20
+ @frame = 0
21
+ begin_frame
22
+ end
23
+
24
+ # The dots of a line, at least Painter::MIN_LINE_DOTS.
25
+ def line_dots
26
+ dots = (@registers[0] + 1) * VDC.char_dots(@registers)
27
+ [dots, Painter::MIN_LINE_DOTS].max
28
+ end
29
+
30
+ # Outside the displayed rows, as status bit 5 reads.
31
+ def vertical_blank? = @adjusting || @row >= @registers[6]
32
+
33
+ def save_state(out)
34
+ out.int(@frame).int(@row).int(@row_line).boolean(@adjusting).int(@frame_line).int(@frame_lines)
35
+ out.int(@sync_start).int(@sync_lines)
36
+ end
37
+
38
+ def load_state(input)
39
+ @frame = input.int
40
+ @row = input.int
41
+ @row_line = input.int
42
+ @adjusting = input.boolean?
43
+ @frame_line = input.int
44
+ @frame_lines = input.int
45
+ @sync_start = input.int
46
+ @sync_lines = input.int
47
+ end
48
+
49
+ def end_line(render)
50
+ paint_line if render
51
+ @frame_line += 1
52
+ @row_line += 1
53
+ if @adjusting
54
+ begin_frame(next_frame: true) if @row_line >= (@registers[5] & 0x1f)
55
+ elsif @row_line > (@registers[9] & 0x1f)
56
+ @row_line = 0
57
+ @row += 1
58
+ end_rows if @row > @registers[4]
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def paint_line
65
+ sync = @frame_line >= @sync_start && @frame_line < @sync_start + @sync_lines
66
+ line_y = (@frame_line - @sync_start - @sync_lines) % @frame_lines
67
+ @painter.paint(line_y, @adjusting ? -1 : @row, @row_line, sync, @frame)
68
+ end
69
+
70
+ def end_rows
71
+ return begin_frame(next_frame: true) if @registers[5].nobits?(0x1f)
72
+
73
+ @adjusting = true
74
+ @row_line = 0
75
+ end
76
+
77
+ # The vertical sync starts R7 - 1 rows in, as the Programmer's
78
+ # Reference Guide counts R7 "plus 1", and lasts R3 bits 4-7 lines,
79
+ # or 16 for 0.
80
+ def begin_frame(next_frame: false)
81
+ regs = @registers
82
+ @frame += 1 if next_frame
83
+ @row = 0
84
+ @row_line = 0
85
+ @adjusting = false
86
+ @frame_line = 0
87
+ row_lines = (regs[9] & 0x1f) + 1
88
+ @frame_lines = ((regs[4] + 1) * row_lines) + (regs[5] & 0x1f)
89
+ @sync_start = ((regs[7] - 1) % (regs[4] + 1)) * row_lines
90
+ @sync_lines = regs[3] >> 4
91
+ @sync_lines = 16 if @sync_lines.zero?
92
+ @painter.begin_frame(line_dots, @frame_lines)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class VDC
6
+ # Paints the characters of a displayed line, R1 of them from the
7
+ # line's first dot, in text mode or, with R25 bit 7, bitmap mode.
8
+ #
9
+ # Within a character the pixels come from R22: bits 4-7 hold its width
10
+ # less one (the width itself in double-width mode), and bits 0-3 the
11
+ # last pixel it displays (one more in double-width mode); a value past
12
+ # the width displays all of it. R25 bits 0-3 scroll the line left by
13
+ # the width less one less their value: the first character's pattern
14
+ # loads that many pixels before the display starts, and the displayed
15
+ # pixels run from the load to the R22 bits 0-3 count, as the
16
+ # Programmer's Reference Guide's table for R25 has it. Pixels past a
17
+ # pattern's eighth or the displayed count are the background, or with
18
+ # R25 bit 5, the semigraphic mode, the last displayed pixel repeated.
19
+ #
20
+ # Rows of characters are R9 + 1 lines, of which lines 0 to R23 display
21
+ # the pattern and the rest the background. R24 bits 0-4 scroll the
22
+ # frame up by that many lines. Row r reads its characters from
23
+ # R12/R13 + r * (R1 + R27) and their attributes from R20/R21 likewise,
24
+ # with both start addresses latched as each frame begins.
25
+ #
26
+ # In text mode a character's pattern is 16 bytes in its set, or 32
27
+ # with R9 above 15, from R28 bits 5-7 (bits 6-7 with 32). With
28
+ # attributes, R25 bit 6, bits 0-3 of the attribute are the colour, bit
29
+ # 4 blinks the character into the background, bit 5 underlines it on
30
+ # R29's line, bit 6 reverses it and bit 7 takes it from the second
31
+ # set. Without them the colour is R26 bits 4-7. The cursor, at R14/R15,
32
+ # reverses lines R10 bits 0-4 up to R11 less one, wrapping past the end
33
+ # of the character, and R10 bits 5-6 hold it solid, off, or blinking
34
+ # at 1/16 or 1/32 of the frame rate. Characters blink at 1/16 of the
35
+ # frame rate, or 1/32 with R24 bit 5. R24 bit 6 reverses everything.
36
+ #
37
+ # In bitmap mode scan line n of the frame reads its bytes from
38
+ # R12/R13 + n * (R1 + R27), and the attribute of each character cell
39
+ # holds its foreground in bits 0-3 and background in bits 4-7, or
40
+ # without attributes R26 holds them the other way round. The cursor
41
+ # isn't shown.
42
+ class Window
43
+ def initialize(registers, memory)
44
+ @registers = registers
45
+ @memory = memory
46
+ @display_start = 0
47
+ @attribute_start = 0
48
+ @layout = Array.new(16, 0)
49
+ @pattern = 0
50
+ @foreground = 0
51
+ @background = 0
52
+ end
53
+
54
+ def latch_starts
55
+ regs = @registers
56
+ @display_start = (regs[12] << 8) | regs[13]
57
+ @attribute_start = (regs[20] << 8) | regs[21]
58
+ end
59
+
60
+ def save_state(out)
61
+ out.int(@display_start).int(@attribute_start)
62
+ end
63
+
64
+ def load_state(input)
65
+ @display_start = input.int
66
+ @attribute_start = input.int
67
+ end
68
+
69
+ def paint(line, row, row_line, frame)
70
+ regs = @registers
71
+ lay_out_cell
72
+ height = (regs[9] & 0x1f) + 1
73
+ content = (row * height) + row_line + (regs[24] & 0x1f)
74
+ char_line = content % height
75
+ @gap_line = char_line > (regs[23] & 0x1f)
76
+ @stride = regs[1] + regs[27]
77
+ @attributes_on = regs[25].anybits?(0x40)
78
+ @screen_reverse = regs[24].anybits?(0x40)
79
+ if regs[25].anybits?(0x80)
80
+ paint_bitmap(line, content, content / height)
81
+ else
82
+ paint_text(line, content / height, char_line, frame)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # The pattern bit each pixel of a character shows, 0 for the first,
89
+ # or -1 for the background.
90
+ def lay_out_cell
91
+ regs = @registers
92
+ double = regs[25].anybits?(0x10)
93
+ @dot = double ? 2 : 1
94
+ @cell = double ? [regs[22] >> 4, 1].max : (regs[22] >> 4) + 1
95
+ @layout = Array.new(@cell, -1) if @layout.length < @cell
96
+ scroll = regs[25] & 0x0f
97
+ load = (scroll + 1) % @cell
98
+ @shift = (@cell - 1 - scroll) % @cell
99
+ last_shown = (regs[22] & 0x0f) - (double ? 1 : 0)
100
+ shown = last_shown >= @cell ? @cell : ((last_shown - load) % @cell) + 1
101
+ shown = [shown, 8].min
102
+ semigraphic = regs[25].anybits?(0x20)
103
+ @cell.times { |pixel| @layout[pixel] = cell_bit(pixel, shown, semigraphic) }
104
+ end
105
+
106
+ def cell_bit(pixel, shown, semigraphic)
107
+ return pixel if pixel < shown
108
+
109
+ semigraphic ? shown - 1 : -1
110
+ end
111
+
112
+ def paint_text(line, char_row, char_line, frame)
113
+ regs = @registers
114
+ @screen = @display_start + (char_row * @stride)
115
+ @attributes = @attribute_start + (char_row * @stride)
116
+ tall = (regs[9] & 0x1f) > 15
117
+ @set = tall ? (regs[28] & 0xc0) << 8 : (regs[28] & 0xe0) << 8
118
+ @set_size = tall ? 0x2000 : 0x1000
119
+ @char_size = tall ? 32 : 16
120
+ @pattern_line = tall ? char_line : char_line & 0x0f
121
+ @underline = char_line == (regs[29] & 0x1f)
122
+ @blink_on = frame % (regs[24].anybits?(0x20) ? 32 : 16) < (regs[24].anybits?(0x20) ? 16 : 8)
123
+ @cursor = cursor_shown?(char_line, frame) ? (regs[14] << 8) | regs[15] : -1
124
+ each_cell(line) { |n| text_cell(n) }
125
+ end
126
+
127
+ def paint_bitmap(line, content, char_row)
128
+ @bitmap = @display_start + (content * @stride)
129
+ @attributes = @attribute_start + (char_row * @stride)
130
+ each_cell(line) { |n| bitmap_cell(n) }
131
+ end
132
+
133
+ def text_cell(index)
134
+ address = (@screen + index) & 0xffff
135
+ attribute = @attributes_on ? @memory.fetch((@attributes + index) & 0xffff) : 0
136
+ @pattern = text_pattern(@memory.fetch(address), attribute)
137
+ reverse = attribute.anybits?(0x40) ^ (address == @cursor) ^ @screen_reverse
138
+ foreground = @attributes_on ? attribute & 0x0f : @registers[26] >> 4
139
+ colours(foreground, @registers[26] & 0x0f, reverse)
140
+ end
141
+
142
+ def text_pattern(code, attribute)
143
+ return 0 if attribute.anybits?(0x10) && !@blink_on
144
+ return 0xff if attribute.anybits?(0x20) && @underline
145
+ return 0 if @gap_line
146
+
147
+ set = @set + (attribute.anybits?(0x80) ? @set_size : 0)
148
+ @memory.fetch((set + (code * @char_size) + @pattern_line) & 0xffff)
149
+ end
150
+
151
+ def bitmap_cell(index)
152
+ @pattern = @gap_line ? 0 : @memory.fetch((@bitmap + index) & 0xffff)
153
+ regs = @registers
154
+ if @attributes_on
155
+ attribute = @memory.fetch((@attributes + index) & 0xffff)
156
+ colours(attribute & 0x0f, attribute >> 4, @screen_reverse)
157
+ else
158
+ colours(regs[26] >> 4, regs[26] & 0x0f, @screen_reverse)
159
+ end
160
+ end
161
+
162
+ def colours(foreground, background, reverse)
163
+ @foreground = reverse ? background : foreground
164
+ @background = reverse ? foreground : background
165
+ end
166
+
167
+ # Paints the cells the display shows, one more when the line is
168
+ # scrolled, each after the block sets its pattern and colours.
169
+ def each_cell(line)
170
+ count = @registers[1] + (@shift.positive? ? 1 : 0)
171
+ right = @registers[1] * @cell
172
+ index = 0
173
+ while index < count
174
+ yield index
175
+ paint_cell(line, (index * @cell) - @shift, right)
176
+ index += 1
177
+ end
178
+ end
179
+
180
+ def paint_cell(line, left, right)
181
+ gap = @registers[26] & 0x0f
182
+ pixel = 0
183
+ while pixel < @cell
184
+ x_pos = left + pixel
185
+ line.fill(pixel_colour(@layout[pixel], gap), x_pos * @dot, @dot) if x_pos >= 0 && x_pos < right
186
+ pixel += 1
187
+ end
188
+ end
189
+
190
+ def pixel_colour(bit, gap)
191
+ return gap if bit.negative?
192
+
193
+ @pattern.anybits?(0x80 >> bit) ? @foreground : @background
194
+ end
195
+
196
+ def cursor_shown?(char_line, frame)
197
+ mode = (@registers[10] >> 5) & 3
198
+ return false if mode == 1
199
+ return false if mode == 2 && (frame % 16) >= 8
200
+ return false if mode == 3 && (frame % 32) >= 16
201
+
202
+ cursor_line?(char_line)
203
+ end
204
+
205
+ def cursor_line?(char_line)
206
+ first = @registers[10] & 0x1f
207
+ stop = @registers[11] & 0x1f
208
+ return char_line >= first && char_line < stop if first < stop
209
+
210
+ first > stop && (char_line >= first || char_line < stop)
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end