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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/CLAUDE.md +25 -6
- data/CONTRIBUTING.md +8 -3
- data/README.md +63 -4
- data/doc/pinned-behaviour.md +75 -0
- data/doc/snapshots.md +15 -0
- data/lib/badline/address_bus/pla.rb +3 -16
- data/lib/badline/address_bus.rb +14 -0
- data/lib/badline/c128/banks.rb +35 -0
- data/lib/badline/c128/bus.rb +347 -0
- data/lib/badline/c128/bus_state.rb +53 -0
- data/lib/badline/c128/color_lines.rb +39 -0
- data/lib/badline/c128/cpu.rb +10 -0
- data/lib/badline/c128/drives.rb +95 -0
- data/lib/badline/c128/fast_serial.rb +62 -0
- data/lib/badline/c128/keys.rb +35 -0
- data/lib/badline/c128/mmu.rb +197 -0
- data/lib/badline/c128/mmu_pages.rb +105 -0
- data/lib/badline/c128/model.rb +39 -0
- data/lib/badline/c128/modes.rb +48 -0
- data/lib/badline/c128/saved_state.rb +121 -0
- data/lib/badline/c128/vdc/memory.rb +54 -0
- data/lib/badline/c128/vdc/painter.rb +143 -0
- data/lib/badline/c128/vdc/raster.rb +97 -0
- data/lib/badline/c128/vdc/window.rb +215 -0
- data/lib/badline/c128/vdc.rb +268 -0
- data/lib/badline/c128.rb +322 -0
- data/lib/badline/chrout_trap.rb +3 -0
- data/lib/badline/cia/fast_forward.rb +42 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +2 -0
- data/lib/badline/computer/attachments.rb +10 -13
- data/lib/badline/computer/kernal_traps.rb +29 -0
- data/lib/badline/computer.rb +5 -0
- data/lib/badline/drive/core.rb +172 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +9 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +13 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +34 -0
- data/lib/badline/drive1571.rb +225 -0
- data/lib/badline/frontend/app.rb +35 -22
- data/lib/badline/frontend/boot.rb +31 -1
- data/lib/badline/frontend/controls.rb +11 -0
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/keys.rb +28 -1
- data/lib/badline/frontend/machine_window.rb +16 -13
- data/lib/badline/frontend/menu_media.rb +3 -3
- data/lib/badline/frontend/menu_pages.rb +8 -2
- data/lib/badline/frontend/menu_ports.rb +1 -1
- data/lib/badline/frontend/pause_menu.rb +1 -0
- data/lib/badline/frontend/screen.rb +33 -8
- data/lib/badline/frontend/screens.rb +90 -0
- data/lib/badline/frontend/timeline.rb +15 -4
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/iec_bus.rb +54 -4
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +58 -8
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +5 -0
- data/lib/badline/machine.rb +20 -9
- data/lib/badline/media/true_drive.rb +26 -23
- data/lib/badline/media.rb +8 -6
- data/lib/badline/options/event.rb +5 -1
- data/lib/badline/options/family.rb +52 -0
- data/lib/badline/options/help.rb +7 -2
- data/lib/badline/options/table.rb +8 -2
- data/lib/badline/options/validation.rb +14 -1
- data/lib/badline/options.rb +20 -19
- data/lib/badline/roms/README +36 -0
- data/lib/badline/roms/c128/basic.rom +0 -0
- data/lib/badline/roms/c128/character.rom +0 -0
- data/lib/badline/roms/c128/kernal.rom +0 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +14 -0
- data/lib/badline/snapshot.rb +9 -2
- data/lib/badline/storage/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic/display_state.rb +12 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic.rb +108 -7
- data/lib/badline/vic20/attachments.rb +6 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +111 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- data/lib/badline.rb +1 -0
- 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
|