PageSourceSearch

https://retroapplejs.github.io/res/EMU_CARD_serialpro.js

js retroapplejs.github.io collected 2026-10-03 09:51:04 UTC 165,576 bytes, 4,469 lines download raw bytes

1// 2026 Applied Engineering Serial Pro peripheral for RetroAppleJS.
2// Firmware target: Serial Pro v2.0.
3//
4// EMU_CARD_serialpro.js
5//
6// Implemented in this stage:
7//   - authoritative 8 KiB 27C64 EPROM image
8//   - slot-dependent $Cn00-$CnFF ROM directly from that EPROM
9//   - shared $C800-$CFFF expansion-ROM ownership/release behavior
10//   - Applied Engineering EPROM bank-register clear/write switches
11//   - 6818-compatible RTC / battery RAM address-data interface
12//   - live clock initialized from the browser host's local date/time
13//   - Stage 2A minimal legacy-6551 ACIA register interface
14//   - byte-stream bridge between the 6551 and the Serial Pro oTERM endpoint
15//   - Stage 2C one-byte 6551 receiver holding register / RDRF behavior
16//   - Stage 2D command/control decoding, modem lines and IRQ generation
17//   - cycle-paced 6551 character timing with optional CPU-speed synchronization
18//   - Stage 3A optional browser Web Serial transport for physical USB-UARTs
19//   - 6818 update-ended interrupt generation (UIE/UF/IRQF) on Apple II IRQ
20//
21// Still intentionally deferred:
22//   - 6818 periodic/alarm interrupts and NMI routing
23//   - browser-persistent battery RAM across page reloads
24//   - detailed 6551 receiver-overrun timing
25//   - bit-level parity/framing error injection/validation against an external endpoint
26//   - serial echo/break waveform timing and modem-line UI controls
27//
28// The physical EPROM layout recovered from the Serial Pro v2.0 27C64 is:
29//   $0000-$07FF  HostROM bank 0
30//   $0800-$0FFF  HostROM bank 1
31//   $1000-$17FF  HostROM bank 2
32//   $1800-$18FF  copyright / identification page
33//   $1900-$1FFF  seven 256-byte slot-specific ROM pages (slots 1..7)
34//
35// SHA-1   4f878637ef7f5fa83713482f85369fa72121f898
36// SHA-256 b2346ad096bbfddfb1fac5ad353e325b892fb248d7719714b245560e51e6dc35
37 
38
39const SERIALPRO_EPROM = new Uint8Array([
40    // EPROM $0000-$07FF: HOSTROM BANK 0
41    0x4C,0x19,0xC8,0x08,0x78,0xA9,0x00,0x48,
42    0x20,0x13,0xC8,0x68,0xA4,0x26,0x99,0x87,
43    0xC0,0x28,0x60,0x8A,0xA4,0x26,0x99,0x87,
44    0xC0,0x20,0x57,0xC8,0xA9,0x16,0x20,0x64,
45    0xC8,0xA2,0x00,0x60,0x20,0x57,0xC8,0x4A,
46    0x20,0x85,0xCC,0xBD,0xB8,0x05,0xAA,0x60,
47    0xA5,0x27,0x09,0x80,0xC9,0xA0,0x90,0x08,
48    0x49,0xFF,0xF0,0x08,0xFE,0xB8,0x06,0x60,
49    0x49,0x88,0xD0,0x08,0xDE,0xB8,0x06,0x10,
50    0x03,0x9D,0xB8,0x06,0x60,0x20,0xC0,0xC8,
51    0xAE,0xF8,0x07,0x9D,0xB8,0x05,0x60,0x48,
52    0x84,0x26,0x8E,0xF8,0x07,0xA9,0x00,0x9D,
53    0xB8,0x05,0x68,0x60,0x48,0xA2,0x01,0x20,
54    0x03,0xC8,0xAE,0xF8,0x07,0xA9,0x28,0x20,
55    0x5F,0xCC,0x9D,0x38,0x07,0xA9,0x26,0x20,
56    0x5F,0xCC,0x9D,0xB8,0x03,0xA9,0x22,0x20,
57    0x5F,0xCC,0xF0,0x1C,0x4A,0x90,0x0A,0x4A,
58    0xA9,0x03,0xB0,0x02,0xA9,0x80,0x9D,0xB8,
59    0x04,0xA9,0x25,0x20,0x5F,0xCC,0x9D,0x38,
60    0x06,0x68,0x29,0x95,0x48,0xA9,0x09,0x2C,
61    0xA9,0x01,0x9D,0x38,0x05,0x68,0x1D,0x38,
62    0x07,0x9D,0x38,0x07,0xA9,0x23,0x20,0x5F,
63    0xCC,0x99,0x8B,0xC0,0xA9,0x24,0x20,0x5F,
64    0xCC,0x99,0x8A,0xC0,0xB9,0x88,0xC0,0x60,
65    0x20,0x57,0xC8,0x20,0x54,0xCC,0x29,0x7F,
66    0xB0,0x08,0x85,0x27,0x20,0x57,0xC8,0x20,
67    0xB8,0xCB,0xAC,0xF8,0x07,0xBE,0xB8,0x05,
68    0x60,0x50,0x2D,0xBD,0x38,0x05,0x29,0x7F,
69    0x9D,0x38,0x05,0xA9,0x1F,0x39,0x8A,0xC0,
70    0xD0,0x05,0xA9,0x66,0x20,0x64,0xC8,0xE4,
71    0x37,0xD0,0x0A,0xA9,0x07,0xC5,0x36,0xF0,
72    0x04,0x85,0x36,0xD0,0x0D,0xE4,0x39,0xD0,
73    0x09,0xA9,0x05,0x85,0x38,0x4C,0xAD,0xC9,
74    0xB0,0xFB,0x4C,0x71,0xC9,0xBD,0x38,0x07,
75    0x29,0x0A,0xC9,0x0A,0xD0,0x14,0xA4,0x27,
76    0xBD,0xB8,0x04,0x29,0x7F,0xC0,0x92,0xF0,
77    0x06,0x09,0x80,0xC0,0x94,0xD0,0x03,0x9D,
78    0xB8,0x04,0x60,0x00,0x00,0x00,0x00,0x00,
79    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
80    0x00,0x00,0x00,0x00,0x00,0x2C,0x58,0xFF,
81    0x50,0xB8,0xA9,0x00,0xAE,0xF8,0x07,0x9D,
82    0xB8,0x05,0x8A,0x0A,0x0A,0x0A,0x0A,0x85,
83    0x26,0x50,0x0F,0x20,0x7B,0xCC,0x90,0xFB,
84    0xA0,0x00,0x91,0x3C,0x20,0xBA,0xFC,0x90,
85    0xF2,0x60,0xA0,0x00,0xB1,0x3C,0x85,0x27,
86    0x20,0x6D,0xCC,0x20,0xBA,0xFC,0x90,0xF2,
87    0x60,0xBD,0xB8,0x04,0x0A,0x10,0x12,0x6A,
88    0x29,0xBF,0x9D,0xB8,0x04,0x68,0x48,0xAA,
89    0xA5,0x27,0x09,0x20,0x9D,0x00,0x02,0x85,
90    0x27,0x20,0x88,0xCA,0xF0,0x55,0x20,0xB8,
91    0xCB,0x20,0x88,0xCA,0xF0,0x08,0xBD,0xB8,
92    0x04,0x10,0x03,0x20,0xB4,0xCA,0x68,0xAA,
93    0x68,0xA8,0xA5,0x27,0x60,0x00,0x00,0x00,
94    0x00,0x00,0x4C,0xCA,0xC8,0x20,0x88,0xCA,
95    0xF0,0x0F,0xBD,0xB8,0x06,0x10,0x05,0x5E,
96    0xB8,0x06,0xD0,0x0A,0x20,0xBE,0xCC,0xB0,
97    0x0B,0x20,0x54,0xCB,0x90,0xF6,0x20,0x32,
98    0xCC,0x20,0x0D,0xC9,0xA5,0x27,0xC9,0xE0,
99    0x90,0xBF,0xBD,0xB8,0x03,0x29,0xC0,0xF0,
100    0xB8,0xBD,0xB8,0x04,0x09,0x40,0x9D,0xB8,
101    0x04,0xD0,0xAE,0x20,0x6B,0xCB,0xB0,0xB6,
102    0xA5,0x27,0x48,0xBD,0x38,0x07,0x29,0xC0,
103    0xD0,0x16,0xA5,0x24,0xF0,0x33,0xC9,0x08,
104    0xF0,0x04,0xC9,0x10,0xD0,0x0A,0x09,0xF0,
105    0x3D,0xB8,0x06,0x18,0x65,0x24,0x85,0x24,
106    0xBD,0xB8,0x06,0xC5,0x24,0xF0,0x1A,0xA9,
107    0xA0,0x90,0x08,0xBD,0x38,0x07,0x0A,0x10,
108    0x10,0xA9,0x88,0x85,0x27,0x20,0x30,0xC8,
109    0x20,0xC5,0xCB,0x20,0x75,0xCA,0x4C,0x08,
110    0xCA,0x68,0x20,0x4B,0xCA,0xBD,0x38,0x07,
111    0x30,0x16,0xBC,0xB8,0x06,0x0A,0x30,0x0E,
112    0x98,0xA0,0x00,0x38,0xFD,0x38,0x06,0xC9,
113    0xF8,0x90,0x03,0x69,0x27,0xA8,0x84,0x24,
114    0x4C,0x9E,0xC9,0x85,0x27,0x48,0xBD,0xB8,
115    0x04,0x29,0x30,0xF0,0x0C,0xC9,0x20,0xA5,
116    0x27,0x29,0x7F,0x90,0x02,0x09,0x80,0x85,
117    0x27,0x20,0xBD,0xCB,0x20,0x30,0xC8,0x68,
118    0x85,0x27,0x49,0x8D,0x0A,0xD0,0x06,0x9D,
119    0xB8,0x06,0x85,0x24,0x60,0xBD,0xB8,0x04,
120    0x10,0x0D,0xBD,0x38,0x06,0xF0,0x08,0x18,
121    0xFD,0xB8,0x06,0xA9,0x8D,0x90,0xC4,0x60,
122    0xAE,0xF8,0x07,0xBD,0x38,0x07,0x29,0x02,
123    0x60,0x70,0x13,0x90,0x08,0xA2,0x02,0x20,
124    0x03,0xC8,0x4C,0x9E,0xC9,0xA2,0x0A,0x20,
125    0x03,0xC8,0x68,0x4C,0xA0,0xC9,0xA2,0x12,
126    0x20,0x03,0xC8,0x4C,0x9E,0xC9,0xA2,0x09,
127    0x20,0x03,0xC8,0x60,0xA9,0x7F,0x3D,0x38,
128    0x07,0x9D,0x38,0x07,0xA9,0x00,0x48,0x20,
129    0xDB,0xCA,0x68,0x90,0x0E,0xA8,0x20,0x14,
130    0xCB,0x98,0x90,0xF2,0x48,0x20,0xBD,0xCB,
131    0x4C,0xBF,0xCA,0x20,0x32,0xCC,0xA9,0x8D,
132    0x85,0x27,0x60,0xA4,0x24,0xB1,0x28,0x85,
133    0x27,0xA5,0x4F,0x29,0x03,0xD0,0x0E,0xA4,
134    0x24,0xA9,0xDF,0xD1,0x28,0xD0,0x02,0xA5,
135    0x27,0x91,0x28,0xE6,0x4F,0xBD,0xB8,0x04,
136    0x0A,0x90,0x0C,0x20,0x54,0xCB,0xB0,0x08,
137    0x20,0xBE,0xCC,0x90,0xDC,0x70,0xD4,0x60,
138    0x20,0x32,0xCC,0x20,0x0D,0xC9,0x20,0x12,
139    0xCD,0x4C,0xDB,0xCA,0xBD,0x38,0x07,0x0A,
140    0x10,0x2D,0xA5,0x27,0xC9,0x9B,0xD0,0x07,
141    0x88,0x10,0x02,0xA0,0x02,0x18,0x60,0xC0,
142    0x01,0x90,0x1C,0xF0,0x0C,0xC9,0xC1,0x90,
143    0x16,0xC9,0xDB,0xB0,0x12,0x09,0x20,0xD0,
144    0x0A,0x49,0xB0,0xC9,0x0B,0xB0,0x06,0xA8,
145    0xB9,0x49,0xCB,0x85,0x27,0xA0,0x02,0x38,
146    0x60,0x9B,0x9C,0x9F,0xDB,0xDC,0xDF,0xFB,
147    0xFC,0xFD,0xFE,0xFF,0x20,0x7B,0xCC,0x90,
148    0x11,0x09,0x80,0xC9,0x8A,0xD0,0x0A,0x18,
149    0xBD,0x38,0x07,0x29,0x20,0xD0,0x03,0xA9,
150    0x8A,0x38,0x60,0xA9,0x22,0x20,0x5F,0xCC,
151    0x4A,0xB0,0x3A,0xBD,0xB8,0x04,0x29,0x07,
152    0xF0,0x05,0x20,0x77,0xCD,0x38,0x60,0xA5,
153    0x27,0x29,0x7F,0xDD,0x38,0x05,0xD0,0x09,
154    0xFE,0xB8,0x04,0x8A,0x48,0xA9,0x8F,0x48,
155    0x60,0xBD,0x38,0x07,0x29,0x08,0xF0,0x15,
156    0x20,0x54,0xCB,0x90,0x10,0xC9,0x93,0xF0,
157    0x0E,0x48,0xBD,0x38,0x07,0x4A,0x4A,0x68,
158    0x90,0x04,0x9D,0xB8,0x06,0x18,0x60,0x20,
159    0x54,0xCC,0xC9,0x91,0xD0,0xF9,0x18,0x60,
160    0x20,0x6B,0xCB,0xB0,0xFA,0xBD,0x38,0x07,
161    0x10,0x03,0x20,0x12,0xCD,0xA5,0x27,0x48,
162    0xA9,0x22,0x20,0x5F,0xCC,0xC9,0x03,0xD0,
163    0x45,0xBD,0x38,0x04,0xC9,0x99,0xB0,0x10,
164    0xC9,0x95,0x90,0x22,0xC9,0x96,0x68,0x48,
165    0x29,0x7F,0x49,0x1B,0xD0,0x18,0x90,0x19,
166    0xBD,0xB8,0x04,0x29,0x1F,0x09,0x80,0x85,
167    0x27,0x20,0x6D,0xCC,0x20,0x54,0xCC,0x49,
168    0x86,0xD0,0xF6,0x9D,0x38,0x04,0xFE,0x38,
169    0x04,0x68,0x48,0x85,0x27,0x49,0x8D,0x0A,
170    0xD0,0x0C,0xBD,0xB8,0x03,0x29,0x30,0xF0,
171    0x05,0xA9,0xC8,0x9D,0x38,0x04,0x20,0x6D,
172    0xCC,0x20,0xFC,0xCE,0xBD,0x38,0x07,0x4A,
173    0xA5,0x27,0x2A,0xC9,0x1B,0xD0,0x07,0xA9,
174    0x8A,0x85,0x27,0x20,0xC5,0xCB,0x68,0x85,
175    0x27,0x60,0x48,0xA4,0x24,0xA5,0x27,0x91,
176    0x28,0x68,0xC9,0x95,0xD0,0x13,0xA5,0x27,
177    0xC9,0x20,0xB0,0x0D,0xBC,0xB8,0x03,0x10,
178    0x08,0x49,0xC0,0xC0,0xC0,0xB0,0x02,0x49,
179    0x20,0x85,0x27,0x60,0x20,0x54,0xCB,0xB0,
180    0x05,0x20,0xAC,0xCC,0x90,0xF6,0x60,0x08,
181    0x78,0xA4,0x26,0x99,0x84,0xC0,0xB9,0x85,
182    0xC0,0x28,0x09,0x00,0x60,0x18,0x20,0x85,
183    0xCC,0x90,0xFA,0x8A,0x48,0xA9,0x8B,0x48,
184    0xA5,0x27,0x60,0x38,0x20,0x85,0xCC,0x90,
185    0x03,0xB9,0x88,0xC0,0x60,0xA4,0x26,0xB9,
186    0x89,0xC0,0x90,0x18,0x29,0x2F,0xC9,0x20,
187    0x90,0x06,0xC9,0x28,0x09,0x08,0xD0,0x08,
188    0xC9,0x08,0x29,0x07,0xF0,0x05,0x09,0x20,
189    0x9D,0xB8,0x05,0x60,0x29,0x70,0xC9,0x10,
190    0xF0,0x01,0x18,0x60,0x18,0xBD,0x38,0x07,
191    0x29,0x04,0xF0,0x09,0xAD,0x00,0xC0,0x10,
192    0x04,0x8D,0x10,0xC0,0x38,0x60,0xE6,0x4E,
193    0xD0,0x02,0xE6,0x4F,0x20,0xAC,0xCC,0xB8,
194    0x90,0x3F,0x20,0x32,0xCC,0x20,0x6B,0xCB,
195    0x90,0x36,0xA0,0x07,0xB9,0x0A,0xCD,0x85,
196    0x27,0x98,0x8D,0x10,0xC0,0x48,0x20,0x12,
197    0xCD,0x68,0xA8,0x88,0x10,0xEE,0xAD,0x00,
198    0xC0,0x10,0xFB,0x8D,0x10,0xC0,0xC9,0x88,
199    0xF0,0xE0,0x85,0x27,0x20,0x12,0xCD,0x20,
200    0x6B,0xCB,0xBD,0xB8,0x04,0x29,0x07,0xD0,
201    0xE5,0xA9,0x8D,0x85,0x27,0x2C,0x58,0xFF,
202    0x38,0x60,0xBA,0xCF,0xD2,0xD0,0xA0,0xC5,
203    0xC1,0x8D,0xBD,0x38,0x07,0x29,0x02,0xF0,
204    0x0D,0xBD,0xB8,0x04,0x29,0x38,0xF0,0x06,
205    0x8A,0x48,0xA9,0x67,0x48,0x60,0xBD,0xB8,
206    0x03,0x2A,0x2A,0x2A,0x29,0x03,0xA8,0xA5,
207    0x27,0x09,0x80,0xC0,0x01,0xF0,0x16,0xC9,
208    0xC0,0x90,0x12,0xC9,0xE0,0x90,0x08,0x49,
209    0x20,0xC0,0x02,0xD0,0x08,0xF0,0x04,0xC0,
210    0x03,0xD0,0x02,0x29,0x1F,0x4C,0xF6,0xFD,
211    0xAE,0xF8,0x07,0xBD,0x38,0x06,0x85,0x36,
212    0xBD,0xB8,0x04,0x4A,0x4A,0x4A,0x29,0x07,
213    0x09,0xC0,0x85,0x37,0x60,0xA5,0x36,0x9D,
214    0x38,0x06,0x68,0x85,0x36,0x68,0x85,0x37,
215    0x68,0x85,0x38,0x68,0x85,0x39,0x60,0xA8,
216    0xA5,0x27,0x29,0x7F,0xC9,0x20,0xF0,0x29,
217    0xC9,0x0D,0xD0,0x13,0x20,0x11,0xCE,0xC0,
218    0x04,0xF0,0x04,0xC0,0x05,0xD0,0x1A,0xA9,
219    0x2C,0x20,0x5F,0xCC,0x4C,0x8E,0xCE,0xC9,
220    0x60,0x90,0x02,0x29,0x5F,0x88,0xF0,0x0A,
221    0x88,0xF0,0x2D,0x88,0xF0,0x4F,0x88,0xF0,
222    0x79,0x60,0xC9,0x20,0xB0,0x10,0xDD,0x38,
223    0x05,0xD0,0x06,0xDE,0xB8,0x04,0x4C,0x6D,
224    0xCC,0x9D,0x38,0x05,0xD0,0x53,0x49,0x30,
225    0xC9,0x0A,0xB0,0x08,0xFE,0xB8,0x04,0xA0,
226    0x2B,0x4C,0x85,0xCE,0xA0,0xFD,0xD0,0x6D,
227    0x49,0x30,0xC9,0x0A,0xB0,0x1B,0x48,0xA9,
228    0x2B,0x20,0x5F,0xCC,0x0A,0xBA,0x48,0x0A,
229    0x0A,0x7D,0x00,0x01,0xE8,0x7D,0x00,0x01,
230    0x9A,0xAE,0xF8,0x07,0xA0,0x2B,0x4C,0x85,
231    0xCE,0xA0,0x3A,0xD0,0x48,0x4A,0x49,0x22,
232    0xD0,0x3B,0xA9,0x2C,0x20,0x5F,0xCC,0x90,
233    0x05,0x1D,0x38,0x07,0xD0,0x05,0x49,0xFF,
234    0x3D,0x38,0x07,0x9D,0x38,0x07,0xA9,0x06,
235    0x2C,0xA9,0x00,0xAE,0xF8,0x07,0x5D,0xB8,
236    0x04,0x29,0x07,0x5D,0xB8,0x04,0x9D,0xB8,
237    0x04,0x60,0xA0,0x04,0xD9,0xF2,0xCE,0xF0,
238    0x04,0x88,0x10,0xF8,0x60,0xAD,0xF8,0x07,
239    0x48,0xA9,0x93,0x48,0x60,0xA9,0x20,0x9D,
240    0xB8,0x05,0x4C,0x0E,0xCE,0xA5,0x27,0x29,
241    0x7F,0xC9,0x60,0x90,0x02,0x29,0x5F,0x85,
242    0x27,0xC8,0xC8,0xC8,0xB9,0x96,0xCE,0xF0,
243    0xE4,0x45,0x27,0x0A,0xD0,0xF3,0xB9,0x97,
244    0xCE,0x29,0x20,0xD0,0x07,0xBD,0x38,0x07,
245    0x29,0x10,0xD0,0xE5,0xBD,0x38,0x07,0x4A,
246    0x4A,0xB9,0x97,0xCE,0x90,0x01,0x0A,0x10,
247    0xD8,0xB9,0x97,0xCE,0x20,0x13,0xCE,0xB9,
248    0x97,0xCE,0x29,0x10,0xC9,0x10,0xB9,0x98,
249    0xCE,0xB0,0x0B,0xA0,0x2C,0x48,0xAD,0xF8,
250    0x07,0x48,0xA9,0x83,0x48,0x60,0xAA,0x20,
251    0x03,0xC8,0xAE,0xF8,0x07,0x60,0x3F,0xF0,
252    0x09,0x42,0x65,0x69,0x43,0x85,0x51,0x45,
253    0xE3,0x80,0x46,0xE3,0x04,0x47,0xA4,0x1A,
254    0x48,0x90,0x32,0x49,0x90,0x71,0x4B,0x90,
255    0x79,0x4C,0xE3,0x01,0x4D,0xE3,0x20,0x51,
256    0x45,0x61,0x52,0xC5,0x59,0x53,0x43,0x40,
257    0x53,0x90,0x22,0x54,0x45,0x51,0x54,0x83,
258    0x40,0x57,0x90,0x2A,0x58,0xE3,0x08,0x5A,
259    0xE5,0x89,0x00,0x42,0xF6,0x39,0x43,0xF6,
260    0x19,0x44,0xF6,0x49,0x46,0xF6,0x29,0x48,
261    0x90,0x3A,0x4C,0xF6,0x21,0x4E,0x90,0x81,
262    0x50,0xF6,0x41,0x53,0x56,0x31,0x54,0xD6,
263    0x11,0x00,0x53,0x49,0x32,0x4D,0x52,0x80,
264    0x40,0x20,0x02,0x01,0x09,0x80,0xA8,0xBD,
265    0xB8,0x03,0xC0,0x8C,0xF0,0x0C,0xC0,0x8A,
266    0xF0,0x06,0xC0,0x8D,0xD0,0x15,0x4A,0x4A,
267    0x4A,0x4A,0x29,0x03,0xA8,0xB9,0x24,0xCF,
268    0xF0,0x09,0xA8,0xA9,0x08,0x20,0x28,0xCF,
269    0x88,0xD0,0xF8,0x60,0x00,0x04,0x20,0xFA,
270    0x48,0xA9,0x90,0x38,0xE9,0x01,0xD0,0xFB,
271    0x68,0xE9,0x01,0xD0,0xF3,0x60,0xFF,0xFF,
272    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
273    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
274    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
275    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
276    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
277    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
278    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
279    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
280    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
281    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
282    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
283    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
284    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
285    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
286    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
287    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
288    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
289    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
290    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
291    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
292    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
293    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
294    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
295    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
296    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
297    // EPROM $0800-$0FFF: HOSTROM BANK 1
298    0x00,0x00,0x00,0x08,0x78,0xA9,0x01,0x48,
299    0x20,0x13,0xC8,0x68,0xA4,0x26,0x99,0x87,
300    0xC0,0x28,0x60,0x8A,0xA4,0x26,0x99,0x87,
301    0xC0,0x8A,0x4A,0x4A,0x29,0xFE,0xAA,0xBD,
302    0x2C,0xC8,0x48,0xBD,0x2B,0xC8,0x48,0xAE,
303    0xF8,0x07,0x60,0x4E,0xC8,0xAF,0xC8,0x0A,
304    0xCF,0x14,0xCF,0x1F,0xCF,0x28,0xCF,0x3A,
305    0xCF,0x59,0xCF,0x75,0xCF,0x7C,0xCF,0x8F,
306    0xCF,0x96,0xCF,0xA2,0xCF,0xAB,0xCF,0xC8,
307    0xCF,0xD4,0xCF,0xDD,0xCF,0xEB,0xCF,0xA9,
308    0x20,0x20,0x91,0xC8,0xC9,0xAE,0xD0,0x09,
309    0xA9,0x21,0x20,0x91,0xC8,0xC9,0x55,0xF0,
310    0x1C,0xA0,0x20,0xB9,0x7A,0xC8,0x20,0xA7,
311    0xC8,0xC8,0xC0,0x2D,0x90,0xF5,0xA0,0x0A,
312    0xA9,0x20,0x20,0xA7,0xC8,0xC8,0xA9,0x02,
313    0x20,0xA7,0xC8,0xA4,0x26,0xA9,0x00,0x9D,
314    0xB8,0x04,0x9D,0x38,0x04,0x9D,0xB8,0x05,
315    0x9D,0x38,0x06,0x9D,0xB8,0x06,0x60,0xA9,
316    0x2B,0xA4,0x26,0x99,0x84,0xC0,0xB9,0x85,
317    0xC0,0x60,0xAE,0x55,0x02,0x1E,0x0B,0x50,
318    0x00,0x00,0x09,0x06,0x00,0x00,0x00,0x48,
319    0xAD,0xF8,0x07,0x48,0xA9,0x83,0x48,0x60,
320    0x8D,0x0C,0xC0,0x8D,0x00,0xC0,0x8D,0x0E,
321    0xC0,0x20,0x2F,0xFB,0xA4,0x26,0xA9,0x00,
322    0x99,0x8A,0xC0,0x20,0x97,0xCF,0x20,0x84,
323    0xFE,0x20,0x58,0xFC,0x20,0x4F,0xC8,0xA9,
324    0x00,0x85,0x27,0xA9,0x9C,0xA0,0xCC,0x20,
325    0x54,0xCB,0xAD,0xF8,0x07,0x49,0x70,0x20,
326    0xED,0xFD,0xA9,0x14,0x85,0x25,0xA9,0xC2,
327    0xA0,0xCC,0x20,0x54,0xCB,0x20,0xB1,0xC9,
328    0xAD,0x00,0xC0,0x10,0xF8,0x8D,0x10,0xC0,
329    0xC9,0xE0,0x90,0x02,0x29,0xDF,0xA4,0x27,
330    0xC9,0xC9,0xF0,0x68,0xC9,0x8B,0xF0,0x64,
331    0xC9,0xCD,0xF0,0x72,0xC9,0x8D,0xF0,0x6E,
332    0xC9,0x8A,0xF0,0x6A,0xC9,0xCA,0xF0,0x70,
333    0xC9,0x88,0xF0,0x6C,0xC9,0xCB,0xF0,0x71,
334    0xC9,0x95,0xF0,0x6D,0xC9,0xA0,0xD0,0x1D,
335    0xC0,0x1E,0xD0,0x19,0x20,0xE8,0xCA,0x20,
336    0x2D,0xCA,0x20,0x47,0xCB,0x48,0xA9,0x00,
337    0x20,0xD6,0xCA,0x68,0xC9,0x30,0x90,0x05,
338    0xA0,0x1C,0x4C,0x94,0xC9,0xC9,0x9B,0xD0,
339    0xA7,0x85,0x27,0xA0,0x0B,0xA9,0x02,0x20,
340    0xA7,0xC8,0x20,0x58,0xFC,0xA9,0xF3,0xA0,
341    0xCE,0x20,0x54,0xCB,0xAD,0xF3,0x03,0x4D,
342    0xF4,0x03,0xC9,0xA5,0xD0,0x03,0x6C,0xF2,
343    0x03,0x6C,0xFC,0xFF,0x88,0x88,0x10,0x02,
344    0xA0,0x1E,0x84,0x27,0xC0,0x1E,0xF0,0x03,
345    0x20,0x9A,0xC9,0x4C,0xED,0xC8,0xC8,0xC8,
346    0xC0,0x20,0x90,0xEE,0xA0,0x00,0xF0,0xEA,
347    0x20,0x9A,0xC9,0x20,0x5B,0xCA,0x4C,0xED,
348    0xC8,0x20,0x9A,0xC9,0x20,0x77,0xCA,0x4C,
349    0xED,0xC8,0x98,0x48,0xA4,0x27,0x20,0xE8,
350    0xCA,0xA9,0x02,0xC0,0x1E,0xD0,0x02,0xA9,
351    0x82,0xA0,0x0B,0x20,0xA7,0xC8,0x68,0xA8,
352    0x60,0xA9,0x01,0x85,0x25,0xA0,0x00,0x84,
353    0x40,0xC0,0x12,0xD0,0x03,0x20,0x8E,0xFD,
354    0x20,0x2D,0xCA,0x08,0x20,0xF7,0xC9,0x28,
355    0x90,0x1F,0x20,0xE8,0xCA,0x20,0x47,0xCB,
356    0x48,0xA9,0xFF,0xA4,0x40,0xC4,0x27,0xD0,
357    0x02,0xA9,0x3F,0x85,0x32,0x68,0x20,0xDA,
358    0xFD,0xA0,0x01,0x20,0x38,0xCB,0x4C,0xEC,
359    0xC9,0x20,0x12,0xCB,0xA4,0x40,0xC8,0xC8,
360    0xC0,0x20,0x90,0xC3,0x4C,0x84,0xFE,0xA9,
361    0xFF,0x85,0x32,0x20,0x8E,0xFD,0xA0,0x00,
362    0xB1,0x3C,0x85,0x3E,0xC8,0xB1,0x3C,0x85,
363    0x3F,0xA0,0x00,0xA2,0x11,0xB1,0x3E,0x30,
364    0x04,0xCA,0xC8,0xD0,0xF8,0xA9,0xA0,0x20,
365    0xED,0xFD,0xCA,0x10,0xF8,0x20,0x58,0xCB,
366    0xA9,0xBA,0x20,0xED,0xFD,0xA9,0xA0,0x20,
367    0xED,0xFD,0x4C,0xED,0xFD,0x84,0x3C,0xC0,
368    0x0E,0xF0,0x15,0xC0,0x10,0xF0,0x11,0xA4,
369    0x3C,0xB9,0x67,0xCB,0x85,0x3C,0xB9,0x68,
370    0xCB,0xC9,0x80,0x09,0xC0,0x85,0x3D,0x60,
371    0xA9,0x22,0x20,0x91,0xC8,0xD0,0xE8,0xA4,
372    0x3C,0xB9,0x79,0xCB,0x85,0x3C,0xB9,0x7A,
373    0xCB,0xD0,0xE6,0x20,0x2D,0xCA,0xB0,0x4E,
374    0x20,0xF5,0xCA,0x90,0x25,0x88,0x88,0x88,
375    0xC0,0x05,0xB0,0x20,0x8A,0x0A,0x86,0x3E,
376    0x65,0x3E,0x69,0x02,0xA8,0xD0,0x15,0x20,
377    0x2D,0xCA,0xB0,0x47,0x20,0xF5,0xCA,0x90,
378    0x09,0xE0,0x01,0xF0,0x05,0xC8,0xC8,0xC8,
379    0xD0,0x02,0xA0,0x05,0xB1,0x3C,0x48,0xA0,
380    0x02,0xA6,0x26,0xB1,0x3C,0x9D,0x84,0xC0,
381    0x68,0x5D,0x85,0xC0,0xC8,0x31,0x3C,0x48,
382    0x88,0xB1,0x3C,0x9D,0x84,0xC0,0x68,0x5D,
383    0x85,0xC0,0x48,0x4C,0xDB,0xCA,0x20,0x47,
384    0xCB,0xC9,0x00,0xF0,0x0A,0xF8,0x38,0xE9,
385    0x01,0xA0,0x03,0xD1,0x3C,0xB0,0x17,0xA0,
386    0x04,0xD0,0x11,0x20,0x47,0xCB,0xF8,0x18,
387    0x69,0x01,0xA0,0x04,0xD1,0x3C,0x90,0x06,
388    0xF0,0x04,0xA0,0x03,0xB1,0x3C,0xD8,0x48,
389    0x20,0xE8,0xCA,0xA0,0x02,0xB1,0x3C,0xA8,
390    0xAD,0xF8,0x07,0x48,0xA9,0x83,0x48,0x60,
391    0xA9,0x0A,0xA6,0x26,0x9D,0x84,0xC0,0xBD,
392    0x85,0xC0,0x30,0xF4,0x60,0x20,0xE8,0xCA,
393    0x20,0x47,0xCB,0xA0,0x03,0x31,0x3C,0x48,
394    0xA0,0x04,0xB1,0x3C,0xAA,0x68,0xC8,0xD1,
395    0x3C,0xF0,0x06,0xC8,0xC8,0xCA,0xD0,0xF6,
396    0x18,0x60,0xA9,0xFF,0xA4,0x40,0xC4,0x27,
397    0xD0,0x02,0xA9,0x3F,0x85,0x32,0x20,0xF5,
398    0xCA,0xB0,0x08,0xA9,0x2F,0x85,0x3E,0xA9,
399    0xCE,0xD0,0x08,0xC8,0xB1,0x3C,0x85,0x3E,
400    0xC8,0xB1,0x3C,0x85,0x3F,0x20,0x58,0xCB,
401    0xA9,0xFF,0x85,0x32,0xA9,0xA0,0x20,0xED,
402    0xFD,0xC8,0xC0,0x0F,0x90,0xF8,0x60,0xA0,
403    0x02,0xB1,0x3C,0xA6,0x26,0x9D,0x84,0xC0,
404    0xBD,0x85,0xC0,0x60,0x85,0x3E,0x84,0x3F,
405    0xA0,0xFF,0xC8,0xB1,0x3E,0x09,0x80,0x20,
406    0xED,0xFD,0xB1,0x3E,0x10,0xF4,0x60,0x8B,
407    0x4B,0x9C,0x4B,0xCE,0x4B,0xDF,0x4B,0xEA,
408    0x4B,0x28,0x4C,0x39,0x4C,0xFE,0x4B,0x44,
409    0x4C,0x7E,0xCC,0x83,0xCC,0x88,0xCC,0x64,
410    0x4C,0x8D,0xCC,0x92,0xCC,0x97,0xCC,0x12,
411    0x4C,0x1D,0x4C,0x12,0xCD,0x22,0xFF,0x04,
412    0x00,0x7F,0xCD,0x02,0x98,0xCD,0x01,0x8C,
413    0xCD,0x03,0x9F,0xCD,0x1D,0xCD,0x23,0x1F,
414    0x0F,0x11,0xAC,0xCD,0x12,0xAE,0xCD,0x13,
415    0xB0,0xCD,0x14,0xB3,0xCD,0x15,0xB8,0xCD,
416    0x16,0xBB,0xCD,0x17,0xBE,0xCD,0x18,0xC1,
417    0xCD,0x19,0xC5,0xCD,0x1A,0xC9,0xCD,0x1B,
418    0xCD,0xCD,0x1C,0xD1,0xCD,0x1D,0xD5,0xCD,
419    0x1E,0xD9,0xCD,0x1F,0xDD,0xCD,0x26,0xCD,
420    0x23,0x60,0x04,0x00,0xE2,0xCD,0x20,0xE3,
421    0xCD,0x40,0xE4,0xCD,0x60,0xE5,0xCD,0x2F,
422    0xCD,0x23,0x80,0x02,0x00,0xE6,0xCD,0x80,
423    0xE7,0xCD,0x38,0xCD,0x24,0xE0,0x05,0x00,
424    0xE8,0xCD,0x20,0xEC,0xCD,0x60,0xEF,0xCD,
425    0xA0,0xF3,0xCD,0xE0,0xF7,0xCD,0x3E,0xCD,
426    0x25,0xFF,0x05,0x00,0xFC,0xCD,0x28,0x05,
427    0xCE,0x48,0x07,0xCE,0x50,0x09,0xCE,0x84,
428    0x0B,0xCE,0xC8,0xCE,0x28,0x08,0x02,0x00,
429    0xEB,0xCE,0x08,0xE4,0xCE,0xDA,0xCE,0x28,
430    0x80,0x02,0x00,0xEB,0xCE,0x80,0xE4,0xCE,
431    0x49,0xCD,0x26,0x30,0x04,0x00,0x0E,0xCE,
432    0x10,0x16,0xCE,0x20,0x1D,0xCE,0x30,0x25,
433    0xCE,0x51,0xCD,0x28,0x01,0x02,0x00,0x2A,
434    0xCE,0x01,0x2C,0xCE,0x34,0xCE,0x29,0xFF,
435    0x09,0x06,0x75,0xCE,0x01,0x80,0xCE,0x08,
436    0x8F,0xCE,0x03,0x44,0xCE,0x02,0x6F,0xCE,
437    0x04,0x56,0xCE,0x05,0x4D,0xCE,0x00,0x60,
438    0xCE,0x07,0x65,0xCE,0x68,0xCD,0x06,0xFF,
439    0x07,0x01,0x96,0xCE,0x02,0x9C,0xCE,0x03,
440    0xA2,0xCE,0x04,0xA9,0xCE,0x05,0xB2,0xCE,
441    0x06,0xBA,0xCE,0x07,0xC0,0xCE,0x5C,0xCD,
442    0x09,0x01,0x99,0x60,0xCD,0x08,0x01,0x12,
443    0x65,0xCD,0x07,0x01,0x31,0x6F,0xCD,0x04,
444    0x00,0x23,0x73,0xCD,0x02,0x00,0x59,0x79,
445    0xCD,0x00,0x00,0x59,0x41,0x50,0x50,0x4C,
446    0x49,0x45,0x44,0x20,0x45,0x4E,0x47,0x49,
447    0x4E,0x45,0x45,0x52,0x49,0x4E,0x47,0x20,
448    0x53,0x45,0x52,0x49,0x41,0x4C,0x20,0x50,
449    0x52,0x4F,0x2E,0x2E,0x2E,0x53,0x4C,0x4F,
450    0x54,0xA0,0x0D,0x55,0x53,0x45,0x20,0x41,
451    0x52,0x52,0x4F,0x57,0x53,0x20,0x4F,0x52,
452    0x20,0x3C,0x52,0x45,0x54,0x55,0x52,0x4E,
453    0x3E,0x20,0x54,0x4F,0x20,0x53,0x45,0x4C,
454    0x45,0x43,0x54,0x2F,0x4D,0x4F,0x44,0x49,
455    0x46,0x59,0x0D,0x55,0x53,0x45,0x20,0x3C,
456    0x45,0x53,0x43,0x41,0x50,0x45,0x3E,0x20,
457    0x54,0x4F,0x20,0x45,0x58,0x49,0x54,0x20,
458    0x43,0x4F,0x4E,0x46,0x49,0x47,0x55,0x52,
459    0x41,0x54,0x49,0x4F,0x4E,0x20,0x4D,0x45,
460    0x4E,0xD5,0x44,0x45,0x56,0x49,0x43,0x45,
461    0x20,0x54,0x59,0x50,0xC5,0x42,0x41,0x55,
462    0x44,0x20,0x52,0x41,0x54,0xC5,0x44,0x41,
463    0x54,0x41,0x20,0x42,0x49,0x54,0xD3,0x53,
464    0x54,0x4F,0x50,0x20,0x42,0x49,0x54,0xD3,
465    0x50,0x41,0x52,0x49,0x54,0xD9,0x4C,0x49,
466    0x4E,0x45,0x20,0x4C,0x45,0x4E,0x47,0x54,
467    0xC8,0x43,0x52,0x20,0x44,0x45,0x4C,0x41,
468    0xD9,0x47,0x45,0x4E,0x45,0x52,0x41,0x54,
469    0x45,0x20,0x4C,0xC6,0x59,0x45,0x41,0xD2,
470    0x4D,0x4F,0x4E,0x54,0xC8,0x44,0x41,0xD9,
471    0x57,0x45,0x45,0x4B,0x44,0x41,0xD9,0x48,
472    0x4F,0x55,0xD2,0x4D,0x49,0x4E,0x55,0x54,
473    0xC5,0x53,0x45,0x43,0x4F,0x4E,0xC4,0x43,
474    0x4F,0x4D,0x4D,0x55,0x4E,0x49,0x43,0x41,
475    0x54,0x49,0x4F,0xCE,0x50,0x38,0x20,0x45,
476    0x4D,0x55,0x4C,0x41,0x54,0x49,0x4F,0xCE,
477    0x50,0x52,0x49,0x4E,0x54,0x45,0xD2,0x50,
478    0x38,0x41,0x20,0x45,0x4D,0x55,0x4C,0x41,
479    0x54,0x49,0x4F,0xCE,0x35,0xB0,0x37,0xB5,
480    0x31,0x31,0xB0,0x31,0x33,0x34,0x2E,0xB5,
481    0x31,0x35,0xB0,0x33,0x30,0xB0,0x36,0x30,
482    0xB0,0x31,0x32,0x30,0xB0,0x31,0x38,0x30,
483    0xB0,0x32,0x34,0x30,0xB0,0x33,0x36,0x30,
484    0xB0,0x34,0x38,0x30,0xB0,0x37,0x32,0x30,
485    0xB0,0x39,0x36,0x30,0xB0,0x31,0x39,0x32,
486    0x30,0xB0,0xB8,0xB7,0xB6,0xB5,0xB1,0xB2,
487    0x4E,0x4F,0x4E,0xC5,0x4F,0x44,0xC4,0x45,
488    0x56,0x45,0xCE,0x4D,0x41,0x52,0xCB,0x53,
489    0x50,0x41,0x43,0xC5,0x55,0x4E,0x4C,0x49,
490    0x4D,0x49,0x54,0x45,0xC4,0x34,0xB0,0x37,
491    0xB2,0x38,0xB0,0x31,0x33,0xB2,0x4E,0x4F,
492    0x20,0x44,0x45,0x4C,0x41,0xD9,0x33,0x32,
493    0x20,0x4D,0x53,0x45,0xC3,0x32,0x35,0x30,
494    0x20,0x4D,0x53,0x45,0xC3,0x32,0x20,0x53,
495    0x45,0xC3,0x4E,0xCF,0x59,0x45,0xD3,0x45,
496    0x52,0x52,0x4F,0xD2,0x50,0x52,0x49,0x4E,
497    0x54,0x45,0x52,0x20,0x47,0x52,0x41,0x50,
498    0x48,0x49,0x43,0xD3,0x4F,0x54,0x48,0x45,
499    0x52,0x20,0x49,0x44,0xD3,0x4F,0x54,0x48,
500    0x45,0x52,0x20,0x4F,0x4B,0xC9,0x4F,0x4B,
501    0x49,0x44,0x41,0x54,0x41,0x20,0x38,0xB4,
502    0x45,0x50,0x53,0x4F,0xCE,0x4D,0x41,0x4E,
503    0x4E,0x45,0x53,0x4D,0x41,0x4E,0xCE,0x41,
504    0x4E,0x41,0x44,0x45,0xD8,0x49,0x4D,0x41,
505    0x47,0x45,0x57,0x52,0x49,0x54,0x45,0xD2,
506    0x4E,0x45,0x43,0x38,0x30,0x32,0x33,0x2C,
507    0x20,0x43,0x2E,0x49,0x54,0x4F,0xC8,0x49,
508    0x44,0x53,0x20,0x34,0x34,0xB0,0x53,0x55,
509    0x4E,0x44,0x41,0xD9,0x4D,0x4F,0x4E,0x44,
510    0x41,0xD9,0x54,0x55,0x45,0x53,0x44,0x41,
511    0xD9,0x57,0x45,0x44,0x4E,0x45,0x53,0x44,
512    0x41,0xD9,0x54,0x48,0x55,0x52,0x53,0x44,
513    0x41,0xD9,0x46,0x52,0x49,0x44,0x41,0xD9,
514    0x53,0x41,0x54,0x55,0x52,0x44,0x41,0xD9,
515    0x58,0x4F,0x4E,0x2F,0x58,0x4F,0x46,0x46,
516    0x20,0x48,0x41,0x4E,0x44,0x53,0x48,0x41,
517    0x4B,0xC5,0x4C,0x4F,0x43,0x41,0x4C,0x20,
518    0x45,0x43,0x48,0xCF,0x45,0x4E,0x41,0x42,
519    0x4C,0x45,0xC4,0x44,0x49,0x53,0x41,0x42,
520    0x4C,0x45,0xC4,0x43,0x4F,0x4E,0x46,0x49,
521    0x47,0x55,0x52,0x41,0x54,0x49,0x4F,0x4E,
522    0x20,0x43,0x4F,0x4D,0x50,0x4C,0x45,0x54,
523    0x45,0x44,0x8D,0x20,0x8F,0xC8,0x4A,0x6A,
524    0x6A,0xA0,0x03,0xD0,0x19,0x20,0x8F,0xC8,
525    0x0A,0x0A,0x0A,0x0A,0xA0,0x02,0xD0,0x0E,
526    0x20,0x8F,0xC8,0x0A,0x0A,0xA0,0x01,0xD0,
527    0x05,0x20,0x8F,0xC8,0xA0,0x00,0x5D,0xB8,
528    0x03,0x39,0xF5,0xCF,0x5D,0xB8,0x03,0x9D,
529    0xB8,0x03,0x60,0x20,0x8F,0xC8,0x29,0x07,
530    0x0A,0x0A,0x0A,0x0A,0xC5,0x26,0xF0,0x11,
531    0x4A,0x5D,0xB8,0x04,0x29,0x38,0x5D,0xB8,
532    0x04,0x9D,0xB8,0x04,0xA9,0x00,0x9D,0x38,
533    0x06,0x60,0x20,0x8F,0xC8,0x29,0x0F,0xD0,
534    0x07,0xA9,0x23,0x20,0x5F,0xCC,0x29,0x0F,
535    0x09,0x10,0x59,0x8B,0xC0,0x29,0x1F,0x59,
536    0x8B,0xC0,0x99,0x8B,0xC0,0x60,0x20,0x8F,
537    0xC8,0x88,0x4C,0x80,0xCF,0x20,0x8F,0xC8,
538    0x4A,0x6A,0x6A,0x6A,0x59,0x8B,0xC0,0x29,
539    0xE0,0x59,0x8B,0xC0,0x99,0x8B,0xC0,0x60,
540    0xBD,0xB8,0x04,0x09,0x80,0xD0,0x11,0x99,
541    0x89,0xC0,0x20,0x93,0xFE,0x20,0x89,0xFE,
542    0xAE,0xF8,0x07,0xBD,0xB8,0x04,0x29,0x7F,
543    0x9D,0xB8,0x04,0x60,0xB9,0x8A,0xC0,0x48,
544    0x09,0x0C,0x99,0x8A,0xC0,0xA9,0xE9,0x48,    
545    0xA9,0x90,0x38,0xE9,0x01,0xD0,0xFB,0x68,
546    0xE9,0x01,0xD0,0xF3,0x68,0x99,0x8A,0xC0,
547    0x60,0xA9,0x28,0x9D,0x38,0x06,0xBD,0x38,
548    0x07,0x09,0x80,0xD0,0x05,0xA9,0xFE,0x3D,
549    0x38,0x07,0x9D,0x38,0x07,0x60,0x20,0x8F,
550    0xC8,0xC9,0x28,0x90,0xF8,0x9D,0x38,0x06,
551    0xA9,0x3F,0xD0,0xEB,0xBD,0x38,0x05,0x09,
552    0x80,0x9D,0x38,0x05,0x60,0x03,0x0C,0x30,
553    0xC0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
554    // EPROM $1000-$17FF: HOSTROM BANK 2
555    0x00,0x00,0x00,0x08,0x78,0xA9,0x01,0x48,
556    0x20,0x13,0xC8,0x68,0xA4,0x26,0x99,0x87,
557    0xC0,0x28,0x60,0x8A,0xA4,0x26,0x99,0x87,
558    0xC0,0x8A,0x4A,0x4A,0x29,0xFE,0xAA,0xBD,
559    0x2C,0xC8,0x48,0xBD,0x2B,0xC8,0x48,0xAE,
560    0xF8,0x07,0x60,0x2C,0xC9,0x7E,0xC9,0x4A,
561    0xC8,0xE4,0xCA,0x24,0xCE,0x82,0xCE,0x2E,
562    0xCF,0x37,0xCF,0xA4,0x26,0x99,0x84,0xC0,
563    0xB9,0x85,0xC0,0x60,0x48,0x8A,0x48,0xA9,
564    0x83,0x48,0x60,0x20,0xBE,0xDE,0x20,0x7B,
565    0xDD,0x20,0xFD,0xE5,0xA0,0x00,0xB1,0x5E,
566    0x29,0x7F,0xC9,0x21,0xF0,0x57,0x85,0x27,
567    0xAE,0xF8,0x07,0x20,0x2D,0xC9,0x20,0xBE,
568    0xDE,0x20,0xE3,0xDF,0x20,0x6C,0xDD,0xAE,
569    0xF8,0x07,0x20,0x7F,0xC9,0xA2,0x00,0xA0,
570    0x00,0xBD,0x00,0x02,0x29,0x7F,0xC9,0x22,
571    0xF0,0x08,0xC9,0x0D,0xF0,0x09,0x99,0x00,
572    0x02,0xC8,0xE8,0xE0,0x19,0x90,0xEA,0xA9,
573    0x00,0x99,0x00,0x02,0x84,0x9D,0x98,0x20,
574    0x52,0xE4,0xA0,0x00,0x91,0x83,0xC8,0xA5,
575    0x71,0x91,0x83,0xC8,0xA5,0x72,0x91,0x83,
576    0xA0,0x02,0xA2,0x00,0xA5,0x9D,0x20,0xE2,
577    0xE5,0xAE,0xF8,0x07,0x60,0x20,0xBE,0xDE,
578    0x20,0x7B,0xDD,0x20,0xFD,0xE5,0x85,0x9D,
579    0xA0,0x00,0x84,0x83,0x84,0x84,0xB9,0x0A,
580    0xC9,0xF0,0x1B,0x30,0x26,0x29,0x0F,0xA8,
581    0xA5,0x27,0xC0,0x06,0xD0,0x02,0x69,0x00,
582    0xAE,0xF8,0x07,0x20,0x44,0xC8,0xA4,0x84,
583    0xC8,0xC0,0x14,0x90,0xDF,0x60,0x20,0x1E,
584    0xC9,0xB0,0xFA,0xC9,0x0A,0xB0,0xF7,0x85,
585    0x27,0x90,0xEB,0xA5,0x27,0x0A,0x0A,0x0A,
586    0x0A,0x85,0x27,0x20,0x1E,0xC9,0xB0,0xE5,
587    0xC9,0x0A,0xB0,0xE1,0x05,0x27,0x85,0x27,
588    0x90,0xD4,0x00,0xFF,0x49,0x00,0xFF,0x48,
589    0x00,0xFF,0x47,0x00,0x46,0x00,0xFF,0x44,
590    0x00,0xFF,0x42,0x00,0xFF,0x40,0xA4,0x83,
591    0xC4,0x9D,0xB0,0x08,0xE6,0x83,0xB1,0x5E,
592    0x29,0x7F,0x49,0x30,0x60,0xA5,0x27,0xA0,
593    0x1A,0x4C,0x44,0xC8,0x8A,0x48,0xA9,0xA8,
594    0x48,0x60,0xB9,0x85,0xC0,0x38,0xE9,0x01,
595    0x20,0x54,0xCA,0xD0,0x46,0xA4,0x26,0x99,
596    0x84,0xC0,0xC9,0x16,0xF0,0xEC,0xC9,0x14,
597    0xF0,0x03,0xA9,0xFF,0x2C,0xA9,0x3F,0x39,
598    0x85,0xC0,0x20,0x4B,0xCA,0xD0,0x2C,0x98,
599    0x48,0x18,0x79,0x60,0xCA,0xE9,0x3F,0xD0,
600    0x26,0x68,0xA8,0xD0,0x20,0x88,0xF0,0xF9,
601    0x20,0xC3,0xC9,0x20,0x58,0xCA,0xC9,0x8D,
602    0xD0,0x11,0x85,0x27,0x98,0xAA,0x60,0x20,
603    0x34,0xC9,0xA9,0x00,0x85,0x27,0x20,0xA6,
604    0xC9,0xD0,0x04,0xA4,0x2A,0xC8,0x98,0x85,
605    0x2A,0xA8,0xB9,0x60,0xCA,0x30,0xDC,0xC9,
606    0x40,0xB0,0xC4,0xC9,0x10,0xB0,0xA6,0xA8,
607    0xD0,0xCB,0xA9,0x8D,0xD0,0xCD,0xA9,0x1A,
608    0x20,0x3B,0xC8,0x29,0x7F,0xA0,0x06,0xD9,
609    0xBC,0xC9,0xF0,0x04,0x88,0x10,0xF8,0xC8,
610    0xB9,0x60,0xCA,0x60,0x3A,0x3C,0x3E,0x20,
611    0x23,0x25,0x26,0x88,0xF0,0x53,0x88,0xF0,
612    0x65,0xA9,0x0B,0x20,0x3B,0xC8,0x29,0x02,
613    0xD0,0x0D,0xA9,0x04,0x20,0x3B,0xC8,0x30,
614    0x03,0xA9,0xC1,0x60,0xA9,0xD0,0x60,0xA0,
615    0xC1,0xAD,0x0C,0x02,0x0A,0x0A,0x0A,0x0A,
616    0x49,0xB0,0x4D,0x0D,0x02,0xD0,0x04,0xA9,
617    0x12,0xD0,0x0E,0xC9,0x12,0x90,0x0A,0xA0,
618    0xD0,0xC9,0x13,0x90,0x04,0xF8,0xE9,0x12,
619    0xD8,0x48,0x4A,0x4A,0x4A,0x4A,0xD0,0x02,
620    0x49,0x10,0x49,0xB0,0x8D,0x0C,0x02,0x68,
621    0x29,0x0F,0x09,0xB0,0x8D,0x0D,0x02,0x98,
622    0x60,0xE6,0x27,0xAC,0x01,0x02,0xB9,0xFC,
623    0xC9,0x8D,0x01,0x02,0xB9,0x03,0xCA,0x8D,
624    0x02,0x02,0xB9,0x0A,0xCA,0x60,0xAD,0x06,
625    0x02,0xAC,0x05,0x02,0xC0,0xB1,0x90,0x02,
626    0x69,0x09,0xA8,0xB9,0x10,0xCA,0x8D,0x05,
627    0x02,0xB9,0x1C,0xCA,0x8D,0x06,0x02,0xB9,
628    0x28,0xCA,0x60,0x48,0x4A,0x4A,0x4A,0x4A,
629    0x20,0x54,0xCA,0x68,0x29,0x0F,0x09,0xB0,
630    0xA4,0x27,0x99,0x00,0x02,0xE6,0x27,0x60,
631    0x1B,0x39,0x2E,0x14,0x07,0x27,0x35,0x18,
632    0xAC,0xB0,0x16,0xAC,0x17,0xAC,0x14,0xAC,
633    0x12,0xAC,0x10,0x00,0xA2,0x4E,0x6F,0xAE,
634    0x16,0x19,0x00,0xA2,0x16,0xA0,0x45,0xAF,
635    0x19,0x64,0x00,0x18,0xAF,0x17,0x01,0xA2,
636    0x55,0x5C,0xA0,0x04,0xCD,0x00,0xA0,0x4E,
637    0x55,0xA0,0x04,0xCD,0x00,0xA2,0x47,0x4E,
638    0x00,0xA0,0x43,0x4A,0x00,0x16,0x02,0xA0,
639    0x18,0x03,0xA0,0x17,0x01,0xA0,0x14,0xBA,
640    0x12,0xBA,0x10,0x01,0xD3,0xCD,0xD4,0xD7,
641    0xD4,0xC6,0xD3,0xD5,0xCF,0xD5,0xC5,0xC8,
642    0xD2,0xC1,0xCE,0xCE,0xC5,0xC4,0xD5,0xC9,
643    0xD4,0xCA,0xC6,0xCD,0xC1,0xCD,0xCA,0xCA,
644    0xC1,0xD3,0xCF,0xCE,0xC4,0xC1,0xC5,0xC1,
645    0xD0,0xC1,0xD5,0xD5,0xD5,0xC5,0xC3,0xCF,
646    0xC5,0xCE,0xC2,0xD2,0xD2,0xD9,0xCE,0xCC,
647    0xC7,0xD0,0xD4,0xD6,0xC3,0xA2,0x00,0xB5,
648    0x00,0x48,0xE8,0xE0,0x0C,0x90,0xF8,0xA9,
649    0x2A,0x20,0x3B,0xC8,0x29,0xEF,0x85,0x08,
650    0xA9,0x29,0x20,0x3B,0xC8,0x85,0x06,0xAA,
651    0xA9,0x00,0x85,0x00,0xBD,0x0A,0xCE,0x29,
652    0x07,0x85,0x04,0xA9,0x01,0x85,0x09,0x20,
653    0x0E,0xCC,0xA5,0x08,0x4A,0xB0,0x5B,0xA9,
654    0x00,0x85,0x00,0x20,0x13,0xCC,0xA0,0x00,
655    0x84,0x02,0xA9,0xFF,0x48,0xA5,0x04,0x85,
656    0x01,0x20,0x02,0xCD,0x48,0xC6,0x01,0x10,
657    0xF8,0xA9,0x07,0x85,0x03,0xA9,0x00,0xA4,
658    0x04,0xBA,0xE8,0x5E,0x00,0x01,0x2A,0x88,
659    0x10,0xF8,0x20,0xBB,0xCC,0xC6,0x03,0xD0,
660    0xEC,0x68,0x10,0xFD,0xA4,0x02,0xC8,0xC0,
661    0x28,0x90,0xCD,0x20,0x3E,0xCC,0xB0,0xC6,
662    0x38,0xA5,0x00,0x65,0x04,0xC9,0xA0,0x90,
663    0xB8,0xA8,0xA5,0x08,0x29,0xA2,0xC9,0x02,
664    0xF0,0x05,0x98,0xC9,0xC0,0x90,0xAA,0x4C,
665    0xC0,0xCB,0xA0,0x01,0x84,0x03,0x88,0x84,
666    0x02,0x20,0x13,0xCC,0xA9,0xC0,0x85,0x01,
667    0xC6,0x01,0xE6,0x02,0x20,0x02,0xCD,0x48,
668    0xC6,0x02,0x20,0x02,0xCD,0x0A,0x85,0x05,
669    0x68,0xA6,0x03,0x4A,0x66,0x05,0xCA,0xD0,
670    0xFA,0x20,0xE2,0xCC,0x20,0xBB,0xCC,0xA5,
671    0x01,0xD0,0xDD,0x20,0x3E,0xCC,0xB0,0xD4,
672    0xA6,0x04,0xA4,0x03,0xC8,0xC0,0x08,0x90,
673    0x04,0xE6,0x02,0xA0,0x01,0xCA,0x10,0xF4,
674    0x84,0x03,0xA4,0x02,0xC0,0x28,0x90,0xB9,
675    0xA9,0x24,0x20,0x9F,0xCC,0xA5,0x08,0x29,
676    0x02,0xF0,0x2E,0x20,0x01,0xCC,0xA2,0x00,
677    0xBD,0x50,0x06,0xE0,0x28,0x90,0x1A,0x20,
678    0x0B,0xCC,0xBD,0xA8,0x06,0xE0,0x50,0x90,
679    0x10,0x20,0x0B,0xCC,0xBD,0x00,0x07,0xE0,
680    0x78,0x90,0x06,0x20,0x0B,0xCC,0xBD,0x58,
681    0x07,0x20,0xEB,0xCC,0xE8,0xE0,0xA0,0x90,
682    0xD7,0xA2,0x0B,0x68,0x95,0x00,0xCA,0x10,
683    0xFA,0xA9,0x0D,0x20,0xEB,0xCC,0xA9,0x0A,
684    0x4C,0xEB,0xCC,0xF0,0xF4,0x60,0xA9,0x00,
685    0x4C,0x9F,0xCC,0xA9,0x09,0x20,0x9F,0xCC,
686    0x20,0x53,0xCC,0xA9,0x12,0x20,0x9F,0xCC,
687    0xA5,0x08,0x4A,0xA5,0x06,0x2A,0x0A,0xA8,
688    0xA5,0x08,0x10,0x01,0xC8,0xBE,0xB5,0xCD,
689    0xBD,0xD9,0xCD,0xC9,0xFF,0xF0,0x06,0x20,
690    0xEB,0xCC,0xE8,0xD0,0xF3,0x60,0xA5,0x08,
691    0x10,0x0A,0x49,0x30,0x85,0x08,0x29,0x10,
692    0xF0,0x02,0x38,0x60,0xA9,0x1B,0x20,0x9F,
693    0xCC,0x18,0x60,0xA5,0x06,0xC9,0x02,0xF0,
694    0x0D,0xA6,0x09,0xF0,0x08,0xA9,0x20,0x20,
695    0xEB,0xCC,0xCA,0xD0,0xFA,0x60,0xA9,0x3B,
696    0x20,0xEB,0xCC,0xA5,0x09,0x4A,0xA0,0x06,
697    0x65,0x09,0xB0,0x04,0x88,0x10,0xF9,0x2C,
698    0xA9,0xFF,0xA2,0x00,0x20,0x89,0xCC,0xA2,
699    0x01,0x20,0x89,0xCC,0x09,0x30,0x4C,0xEB,
700    0xCC,0xA0,0x2F,0xD0,0x03,0xFD,0x9D,0xCC,
701    0xDD,0x9D,0xCC,0xC8,0xB0,0xF7,0x48,0x98,
702    0x20,0xEB,0xCC,0x68,0x60,0x64,0x0A,0x18,
703    0x65,0x06,0xA8,0xB9,0x41,0xCD,0xA8,0xB9,
704    0x6E,0xCD,0x48,0x29,0x7F,0xF0,0x08,0x84,
705    0x07,0x20,0xEB,0xCC,0xA4,0x07,0xC8,0x68,
706    0x10,0xED,0x60,0xA4,0x06,0xBE,0x0A,0xCE,
707    0x10,0x05,0x85,0x05,0x20,0xE2,0xCC,0x24,
708    0x08,0x50,0x02,0x49,0xFF,0x39,0x1C,0xCE,
709    0x19,0x13,0xCE,0xC9,0x03,0xD0,0x08,0xBE,
710    0x01,0xCE,0xF0,0x03,0x20,0xEB,0xCC,0x4C,
711    0xEB,0xCC,0xA6,0x04,0x46,0x05,0x2A,0xCA,
712    0x10,0xFA,0x60,0x85,0x27,0xA4,0x26,0xB9,
713    0x89,0xC0,0x29,0x70,0xC9,0x10,0xD0,0xF7,
714    0xAD,0xF8,0x07,0x48,0xA9,0x8B,0x48,0xA5,
715    0x27,0x60,0xA4,0x02,0xC0,0x28,0xB0,0x30,
716    0xA5,0x00,0x65,0x01,0xC9,0xC0,0xB0,0x28,
717    0x48,0x29,0xC7,0x85,0x0A,0x09,0x08,0x85,
718    0x0B,0x68,0x0A,0x0A,0x66,0x0A,0x0A,0x26,
719    0x0B,0x66,0x0A,0x0A,0x26,0x0B,0x0A,0x66,
720    0x0A,0xA5,0x08,0x29,0x20,0xF0,0x06,0xA5,
721    0x0B,0x49,0x60,0x85,0x0B,0xB1,0x0A,0x60,
722    0xA9,0xFF,0x24,0x08,0x70,0x02,0xA9,0x00,
723    0x60,0x02,0x06,0x0C,0x01,0x10,0x01,0x04,
724    0x01,0x01,0x23,0x27,0x28,0x2B,0x27,0x2C,
725    0x27,0x27,0x00,0x14,0x16,0x00,0x1C,0x19,
726    0x1C,0x1D,0x20,0x1C,0x01,0x01,0x30,0x31,
727    0x01,0x31,0x01,0x01,0x34,0x38,0x3A,0x3D,
728    0x3E,0x40,0x3E,0x42,0x00,0x00,0x80,0x8D,
729    0x1B,0xA3,0x1B,0x3E,0x1B,0x4E,0x1B,0x54,
730    0x31,0xB6,0x12,0x0D,0x1C,0xC0,0x1B,0x25,
731    0x39,0x8E,0x1B,0xCB,0x1B,0x53,0xB0,0x1B,
732    0x25,0xB2,0x83,0x1B,0x47,0xB0,0x1B,0x25,
733    0xB5,0x1B,0x41,0x08,0x8A,0x8A,0x30,0x40,
734    0xB6,0x8E,0x03,0x0E,0x03,0x82,0xB0,0x03,
735    0x02,0x8D,0x03,0x0B,0x03,0x82,0x1B,0xB2,
736    0x0F,0x1B,0xC1,0x9D,0x03,0x82,0x1B,0xB6,
737    0x0F,0x1B,0x41,0x1B,0xBC,0x00,0x03,0x06,
738    0x09,0x0C,0x10,0x14,0x18,0x27,0x27,0x27,
739    0x27,0x27,0x27,0x27,0x27,0x1C,0x1F,0x22,
740    0x25,0x27,0x27,0x27,0x27,0x0C,0x10,0x14,
741    0x18,0x00,0x03,0x06,0x09,0x27,0x27,0x27,
742    0x27,0x18,0x01,0xFF,0x30,0x02,0xFF,0xC0,
743    0x00,0xFF,0x80,0x01,0xFF,0x32,0x38,0x30,
744    0xFF,0x35,0x36,0x30,0xFF,0x31,0x39,0x32,
745    0xFF,0x33,0x38,0x34,0xFF,0x02,0x18,0xFF,
746    0x04,0x30,0xFF,0x01,0x40,0xFF,0x03,0x00,
747    0xFF,0x00,0x00,0x00,0x03,0x00,0x03,0x00,
748    0x00,0x03,0x07,0x87,0x05,0x86,0x06,0x86,
749    0x87,0x07,0x85,0x00,0x00,0xC0,0x00,0x00,
750    0x00,0x00,0x00,0x00,0xFF,0xFF,0x3F,0x7F,
751    0x7F,0x7F,0xFF,0xFF,0x3F,0xA5,0x29,0x48,
752    0xA5,0x28,0x48,0xA9,0x00,0x85,0x28,0xA9,
753    0x04,0x85,0x29,0x20,0x01,0xCC,0xA0,0x00,
754    0x20,0x1C,0xCF,0x6A,0x10,0x03,0x8D,0x55,
755    0xC0,0x48,0x84,0x3C,0xB1,0x28,0x8D,0x54,
756    0xC0,0x48,0x20,0x1C,0xCF,0x0A,0x68,0x90,
757    0x02,0x09,0x80,0xC9,0xFF,0xD0,0x02,0xA9,
758    0xA0,0xC9,0xE0,0xB0,0x08,0x09,0xC0,0xC9,
759    0xE0,0x90,0x02,0x49,0x40,0x20,0xEB,0xCC,
760    0xA4,0x3C,0x68,0x49,0x80,0x10,0xD2,0xC8,
761    0xC0,0x28,0x90,0xC4,0x20,0xFF,0xCE,0x90,
762    0xBA,0x20,0x01,0xCC,0x68,0x85,0x28,0x68,
763    0x85,0x29,0x60,0xA2,0x0A,0xB5,0x3A,0x48,
764    0xBD,0xF5,0xCE,0x95,0x3A,0xCA,0x10,0xF5,
765    0xA2,0x0D,0x8E,0xB0,0xC0,0xAD,0xB1,0xC0,
766    0x85,0x41,0xCA,0x8E,0xB0,0xC0,0xAD,0xB1,
767    0xC0,0x85,0x44,0xA9,0x18,0x48,0xAE,0xF8,
768    0x07,0x20,0x01,0xCC,0xA9,0x50,0x48,0x20,
769    0xDD,0xCE,0x09,0x80,0xC9,0xA0,0xB0,0x02,
770    0x09,0xC0,0x20,0xEB,0xCC,0xE6,0x41,0xD0,
771    0x02,0xE6,0x44,0x68,0x38,0xE9,0x01,0xD0,
772    0xE5,0x68,0x38,0xE9,0x01,0xD0,0xD6,0x20,
773    0x01,0xCC,0xA2,0x00,0x68,0x95,0x3A,0xE8,
774    0xE0,0x0B,0x90,0xF8,0x60,0xA5,0x44,0x0A,
775    0x29,0x0C,0xAA,0xBD,0xB0,0xC0,0x46,0x42,
776    0xA5,0x44,0x4A,0x26,0x42,0xAD,0xF8,0x07,
777    0x48,0xA9,0xC6,0x48,0x60,0xAD,0xFF,0xCF,
778    0xAD,0x00,0xC3,0xAD,0x00,0xCC,0x60,0x18,
779    0xA5,0x28,0x49,0x80,0x85,0x28,0x30,0x13,
780    0xE6,0x29,0xA5,0x29,0xC9,0x08,0x90,0x0B,
781    0x4A,0x85,0x29,0xA5,0x28,0x69,0x28,0x85,
782    0x28,0xC9,0x78,0x60,0xAD,0xB3,0xFB,0x49,
783    0x06,0xD0,0x08,0xAD,0x1F,0xC0,0x0A,0xAD,
784    0x1E,0xC0,0x60,0xA9,0x00,0x18,0x60,0xBD,
785    0xB8,0x04,0x29,0xCF,0x9D,0xB8,0x04,0x60,
786    0xA9,0x2B,0x20,0x3B,0xC8,0x29,0x01,0x18,
787    0x69,0x01,0x0A,0x0A,0x0A,0x0A,0x1D,0xB8,
788    0x04,0x9D,0xB8,0x04,0x60,0xFF,0xFF,0xFF,
789    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
790    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
791    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
792    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
793    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
794    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
795    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
796    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
797    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
798    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
799    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
800    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
801    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
802    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
803    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
804    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
805    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
806    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
807    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
808    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
809    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
810    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
811    // EPROM $1800-$18FF: COPYRIGHT / IDENTIFICATION PAGE
812    0x43,0x4F,0x50,0x59,0x52,0x49,0x47,0x48,
813    0x54,0x20,0x28,0x43,0x29,0x20,0x31,0x39,
814    0x38,0x36,0x20,0x41,0x50,0x50,0x4C,0x49,
815    0x45,0x44,0x20,0x45,0x4E,0x47,0x49,0x4E,
816    0x45,0x45,0x52,0x49,0x4E,0x47,0x20,0x43,
817    0x4F,0x50,0x59,0x52,0x49,0x47,0x48,0x54,
818    0x20,0x28,0x43,0x29,0x20,0x31,0x39,0x38,
819    0x36,0x20,0x41,0x50,0x50,0x4C,0x49,0x45,
820    0x44,0x20,0x45,0x4E,0x47,0x49,0x4E,0x45,
821    0x45,0x52,0x49,0x4E,0x47,0x20,0x43,0x4F,
822    0x50,0x59,0x52,0x49,0x47,0x48,0x54,0x20,
823    0x28,0x43,0x29,0x20,0x31,0x39,0x38,0x36,
824    0x20,0x41,0x50,0x50,0x4C,0x49,0x45,0x44,
825    0x20,0x45,0x4E,0x47,0x49,0x4E,0x45,0x45,
826    0x52,0x49,0x4E,0x47,0x20,0x43,0x4F,0x50,
827    0x59,0x52,0x49,0x47,0x48,0x54,0x20,0x28,
828    0x43,0x29,0x20,0x31,0x39,0x38,0x36,0x20,
829    0x41,0x50,0x50,0x4C,0x49,0x45,0x44,0x20,
830    0x45,0x4E,0x47,0x49,0x4E,0x45,0x45,0x52,
831    0x49,0x4E,0x47,0x20,0x43,0x4F,0x50,0x59,
832    0x52,0x49,0x47,0x48,0x54,0x20,0x28,0x43,
833    0x29,0x20,0x31,0x39,0x38,0x36,0x20,0x41,
834    0x50,0x50,0x4C,0x49,0x45,0x44,0x20,0x45,
835    0x4E,0x47,0x49,0x4E,0x45,0x45,0x52,0x49,
836    0x4E,0x47,0x20,0x43,0x4F,0x50,0x59,0x52,
837    0x49,0x47,0x48,0x54,0x20,0x28,0x43,0x29,
838    0x20,0x31,0x39,0x38,0x36,0x20,0x41,0x50,
839    0x50,0x4C,0x49,0x45,0x44,0x20,0x45,0x4E,
840    0x47,0x49,0x4E,0x45,0x45,0x52,0x49,0x4E,
841    0x47,0x20,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
842    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
843    0xFF,0xFF,0xFF,0xFF,0xA4,0x5D,0xFF,0xFF,
844
845    // EPROM $1900-$19FF: SLOT 1 ROM ($C100-$C1FF)
846    0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18,
847    0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A,
848    0x40,0x20,0x46,0xC1,0x4C,0xAE,0xCA,0x20,
849    0x46,0xC1,0x4C,0x1A,0xC1,0x18,0x90,0x01,
850    0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48,
851    0x20,0x46,0xC1,0x4C,0x91,0xCA,0x20,0x46,
852    0xC1,0x4C,0x19,0xC8,0x20,0x46,0xC1,0x4C,
853    0xC0,0xC8,0x20,0x46,0xC1,0x4C,0xCA,0xC8,
854    0x20,0x46,0xC1,0x4C,0x24,0xC8,0x08,0x78,
855    0x8D,0xFF,0xCF,0x8D,0x96,0xC0,0xAE,0x95,
856    0xC0,0xA2,0xC1,0xA0,0x10,0x8E,0xF8,0x07,
857    0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48,
858    0x8A,0x48,0x20,0x46,0xC1,0x4C,0xD9,0xC8,
859    0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA,
860    0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80,
861    0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46,
862    0xC1,0x4C,0x65,0xCD,0x8C,0x94,0xC0,0x68,
863    0x8D,0x95,0xC0,0x60,0x8D,0x98,0xC0,0x60,
864    0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0x94,
865    0xC0,0xAD,0x95,0xC0,0x19,0xF7,0xCE,0xA0,
866    0x2A,0x8C,0x94,0xC0,0x8D,0x95,0xC0,0x38,
867    0x60,0xA2,0x19,0xA0,0x0A,0x8C,0x94,0xC0,
868    0xAD,0x95,0xC0,0x30,0xF8,0x88,0x8C,0x94,
869    0xC0,0xAD,0x95,0xC0,0x8E,0x94,0xC0,0x8D,
870    0x95,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20,
871    0x3A,0x00,0x48,0x20,0x46,0xC1,0xA9,0x02,
872    0x8D,0x97,0xC0,0x68,0x60,0xFF,0xFF,0xFF,
873    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
874    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
875    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
876    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C,
877    0xC6,0xC1,0x4C,0x22,0xC1,0xC1,0xC5,0x20,
878
879    // EPROM $1A00-$1AFF: SLOT 2 ROM ($C200-$C2FF)
880    0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18,
881    0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A,
882    0x40,0x20,0x46,0xC2,0x4C,0xAE,0xCA,0x20,
883    0x46,0xC2,0x4C,0x1A,0xC2,0x18,0x90,0x01,
884    0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48,
885    0x20,0x46,0xC2,0x4C,0x91,0xCA,0x20,0x46,
886    0xC2,0x4C,0x19,0xC8,0x20,0x46,0xC2,0x4C,
887    0xC0,0xC8,0x20,0x46,0xC2,0x4C,0xCA,0xC8,
888    0x20,0x46,0xC2,0x4C,0x24,0xC8,0x08,0x78,
889    0x8D,0xFF,0xCF,0x8D,0xA6,0xC0,0xAE,0xA5,
890    0xC0,0xA2,0xC2,0xA0,0x20,0x8E,0xF8,0x07,
891    0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48,
892    0x8A,0x48,0x20,0x46,0xC2,0x4C,0xD9,0xC8,
893    0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA,
894    0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80,
895    0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46,
896    0xC2,0x4C,0x65,0xCD,0x8C,0xA4,0xC0,0x68,
897    0x8D,0xA5,0xC0,0x60,0x8D,0xA8,0xC0,0x60,
898    0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xA4,
899    0xC0,0xAD,0xA5,0xC0,0x19,0xF7,0xCE,0xA0,
900    0x2A,0x8C,0xA4,0xC0,0x8D,0xA5,0xC0,0x38,
901    0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xA4,0xC0,
902    0xAD,0xA5,0xC0,0x30,0xF8,0x88,0x8C,0xA4,
903    0xC0,0xAD,0xA5,0xC0,0x8E,0xA4,0xC0,0x8D,
904    0xA5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20,
905    0x3A,0x00,0x48,0x20,0x46,0xC2,0xA9,0x02,
906    0x8D,0xA7,0xC0,0x68,0x60,0xFF,0xFF,0xFF,
907    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
908    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
909    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
910    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C,
911    0xC6,0xC2,0x4C,0x22,0xC2,0xC1,0xC5,0x20,
912
913    // EPROM $1B00-$1BFF: SLOT 3 ROM ($C300-$C3FF)
914    0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18,
915    0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A,
916    0x40,0x20,0x46,0xC3,0x4C,0xAE,0xCA,0x20,
917    0x46,0xC3,0x4C,0x1A,0xC3,0x18,0x90,0x01,
918    0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48,
919    0x20,0x46,0xC3,0x4C,0x91,0xCA,0x20,0x46,
920    0xC3,0x4C,0x19,0xC8,0x20,0x46,0xC3,0x4C,
921    0xC0,0xC8,0x20,0x46,0xC3,0x4C,0xCA,0xC8,
922    0x20,0x46,0xC3,0x4C,0x24,0xC8,0x08,0x78,
923    0x8D,0xFF,0xCF,0x8D,0xB6,0xC0,0xAE,0xB5,
924    0xC0,0xA2,0xC3,0xA0,0x30,0x8E,0xF8,0x07,
925    0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48,
926    0x8A,0x48,0x20,0x46,0xC3,0x4C,0xD9,0xC8,
927    0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA,
928    0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80,
929    0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46,
930    0xC3,0x4C,0x65,0xCD,0x8C,0xB4,0xC0,0x68,
931    0x8D,0xB5,0xC0,0x60,0x8D,0xB8,0xC0,0x60,
932    0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xB4,
933    0xC0,0xAD,0xB5,0xC0,0x19,0xF7,0xCE,0xA0,
934    0x2A,0x8C,0xB4,0xC0,0x8D,0xB5,0xC0,0x38,
935    0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xB4,0xC0,
936    0xAD,0xB5,0xC0,0x30,0xF8,0x88,0x8C,0xB4,
937    0xC0,0xAD,0xB5,0xC0,0x8E,0xB4,0xC0,0x8D,
938    0xB5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20,
939    0x3A,0x00,0x48,0x20,0x46,0xC3,0xA9,0x02,
940    0x8D,0xB7,0xC0,0x68,0x60,0xFF,0xFF,0xFF,
941    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
942    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
943    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
944    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C,
945    0xC6,0xC3,0x4C,0x22,0xC3,0xC1,0xC5,0x20,
946
947    // EPROM $1C00-$1CFF: SLOT 4 ROM ($C400-$C4FF)
948    0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18,
949    0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A,
950    0x40,0x20,0x46,0xC4,0x4C,0xAE,0xCA,0x20,
951    0x46,0xC4,0x4C,0x1A,0xC4,0x18,0x90,0x01,
952    0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48,
953    0x20,0x46,0xC4,0x4C,0x91,0xCA,0x20,0x46,
954    0xC4,0x4C,0x19,0xC8,0x20,0x46,0xC4,0x4C,
955    0xC0,0xC8,0x20,0x46,0xC4,0x4C,0xCA,0xC8,
956    0x20,0x46,0xC4,0x4C,0x24,0xC8,0x08,0x78,
957    0x8D,0xFF,0xCF,0x8D,0xC6,0xC0,0xAE,0xC5,
958    0xC0,0xA2,0xC4,0xA0,0x40,0x8E,0xF8,0x07,
959    0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48,
960    0x8A,0x48,0x20,0x46,0xC4,0x4C,0xD9,0xC8,
961    0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA,
962    0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80,
963    0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46,
964    0xC4,0x4C,0x65,0xCD,0x8C,0xC4,0xC0,0x68,
965    0x8D,0xC5,0xC0,0x60,0x8D,0xC8,0xC0,0x60,
966    0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xC4,
967    0xC0,0xAD,0xC5,0xC0,0x19,0xF7,0xCE,0xA0,
968    0x2A,0x8C,0xC4,0xC0,0x8D,0xC5,0xC0,0x38,
969    0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xC4,0xC0,
970    0xAD,0xC5,0xC0,0x30,0xF8,0x88,0x8C,0xC4,
971    0xC0,0xAD,0xC5,0xC0,0x8E,0xC4,0xC0,0x8D,
972    0xC5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20,
973    0x3A,0x00,0x48,0x20,0x46,0xC4,0xA9,0x02,
974    0x8D,0xC7,0xC0,0x68,0x60,0xFF,0xFF,0xFF,
975    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
976    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
977    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
978    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C,
979    0xC6,0xC4,0x4C,0x22,0xC4,0xC1,0xC5,0x20,
980
981    // EPROM $1D00-$1DFF: SLOT 5 ROM ($C500-$C5FF)
982    0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18,
983    0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A,
984    0x40,0x20,0x46,0xC5,0x4C,0xAE,0xCA,0x20,
985    0x46,0xC5,0x4C,0x1A,0xC5,0x18,0x90,0x01,
986    0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48,
987    0x20,0x46,0xC5,0x4C,0x91,0xCA,0x20,0x46,
988    0xC5,0x4C,0x19,0xC8,0x20,0x46,0xC5,0x4C,
989    0xC0,0xC8,0x20,0x46,0xC5,0x4C,0xCA,0xC8,
990    0x20,0x46,0xC5,0x4C,0x24,0xC8,0x08,0x78,
991    0x8D,0xFF,0xCF,0x8D,0xD6,0xC0,0xAE,0xD5,
992    0xC0,0xA2,0xC5,0xA0,0x50,0x8E,0xF8,0x07,
993    0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48,
994    0x8A,0x48,0x20,0x46,0xC5,0x4C,0xD9,0xC8,
995    0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA,
996    0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80,
997    0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46,
998    0xC5,0x4C,0x65,0xCD,0x8C,0xD4,0xC0,0x68,
999    0x8D,0xD5,0xC0,0x60,0x8D,0xD8,0xC0,0x60,
1000    0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xD4,
1001    0xC0,0xAD,0xD5,0xC0,0x19,0xF7,0xCE,0xA0,
1002    0x2A,0x8C,0xD4,0xC0,0x8D,0xD5,0xC0,0x38,
1003    0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xD4,0xC0,
1004    0xAD,0xD5,0xC0,0x30,0xF8,0x88,0x8C,0xD4,
1005    0xC0,0xAD,0xD5,0xC0,0x8E,0xD4,0xC0,0x8D,
1006    0xD5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20,
1007    0x3A,0x00,0x48,0x20,0x46,0xC5,0xA9,0x02,
1008    0x8D,0xD7,0xC0,0x68,0x60,0xFF,0xFF,0xFF,
1009    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1010    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1011    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1012    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C,
1013    0xC6,0xC5,0x4C,0x22,0xC5,0xC1,0xC5,0x20,
1014
1015    // EPROM $1E00-$1EFF: SLOT 6 ROM ($C600-$C6FF)
1016    0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18,
1017    0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A,
1018    0x40,0x20,0x46,0xC6,0x4C,0xAE,0xCA,0x20,
1019    0x46,0xC6,0x4C,0x1A,0xC6,0x18,0x90,0x01,
1020    0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48,
1021    0x20,0x46,0xC6,0x4C,0x91,0xCA,0x20,0x46,
1022    0xC6,0x4C,0x19,0xC8,0x20,0x46,0xC6,0x4C,
1023    0xC0,0xC8,0x20,0x46,0xC6,0x4C,0xCA,0xC8,
1024    0x20,0x46,0xC6,0x4C,0x24,0xC8,0x08,0x78,
1025    0x8D,0xFF,0xCF,0x8D,0xE6,0xC0,0xAE,0xE5,
1026    0xC0,0xA2,0xC6,0xA0,0x60,0x8E,0xF8,0x07,
1027    0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48,
1028    0x8A,0x48,0x20,0x46,0xC6,0x4C,0xD9,0xC8,
1029    0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA,
1030    0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80,
1031    0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46,
1032    0xC6,0x4C,0x65,0xCD,0x8C,0xE4,0xC0,0x68,
1033    0x8D,0xE5,0xC0,0x60,0x8D,0xE8,0xC0,0x60,
1034    0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xE4,
1035    0xC0,0xAD,0xE5,0xC0,0x19,0xF7,0xCE,0xA0,
1036    0x2A,0x8C,0xE4,0xC0,0x8D,0xE5,0xC0,0x38,
1037    0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xE4,0xC0,
1038    0xAD,0xE5,0xC0,0x30,0xF8,0x88,0x8C,0xE4,
1039    0xC0,0xAD,0xE5,0xC0,0x8E,0xE4,0xC0,0x8D,
1040    0xE5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20,
1041    0x3A,0x00,0x48,0x20,0x46,0xC6,0xA9,0x02,
1042    0x8D,0xE7,0xC0,0x68,0x60,0xFF,0xFF,0xFF,
1043    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1044    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1045    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1046    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C,
1047    0xC6,0xC6,0x4C,0x22,0xC6,0xC1,0xC5,0x20,
1048
1049    // EPROM $1F00-$1FFF: SLOT 7 ROM ($C700-$C7FF)
1050    0x2C,0x58,0xFF,0x70,0x57,0x38,0x90,0x18,
1051    0xB8,0x50,0x51,0x01,0x31,0x2E,0x34,0x3A,
1052    0x40,0x20,0x46,0xC7,0x4C,0xAE,0xCA,0x20,
1053    0x46,0xC7,0x4C,0x1A,0xC7,0x18,0x90,0x01,
1054    0x38,0xB8,0x85,0x27,0x98,0x48,0x8A,0x48,
1055    0x20,0x46,0xC7,0x4C,0x91,0xCA,0x20,0x46,
1056    0xC7,0x4C,0x19,0xC8,0x20,0x46,0xC7,0x4C,
1057    0xC0,0xC8,0x20,0x46,0xC7,0x4C,0xCA,0xC8,
1058    0x20,0x46,0xC7,0x4C,0x24,0xC8,0x08,0x78,
1059    0x8D,0xFF,0xCF,0x8D,0xF6,0xC0,0xAE,0xF5,
1060    0xC0,0xA2,0xC7,0xA0,0x70,0x8E,0xF8,0x07,
1061    0x84,0x26,0x28,0x60,0x85,0x27,0x98,0x48,
1062    0x8A,0x48,0x20,0x46,0xC7,0x4C,0xD9,0xC8,
1063    0xA2,0x03,0xB5,0x36,0x48,0xCA,0x10,0xFA,
1064    0x20,0x50,0xCD,0xA5,0x27,0x48,0x09,0x80,
1065    0x20,0xED,0xFD,0x68,0x85,0x27,0x20,0x46,
1066    0xC7,0x4C,0x65,0xCD,0x8C,0xF4,0xC0,0x68,
1067    0x8D,0xF5,0xC0,0x60,0x8D,0xF8,0xC0,0x60,
1068    0xA9,0x00,0xF0,0x0B,0xA9,0x2A,0x8D,0xF4,
1069    0xC0,0xAD,0xF5,0xC0,0x19,0xF7,0xCE,0xA0,
1070    0x2A,0x8C,0xF4,0xC0,0x8D,0xF5,0xC0,0x38,
1071    0x60,0xA2,0x19,0xA0,0x0A,0x8C,0xF4,0xC0,
1072    0xAD,0xF5,0xC0,0x30,0xF8,0x88,0x8C,0xF4,
1073    0xC0,0xAD,0xF5,0xC0,0x8E,0xF4,0xC0,0x8D,
1074    0xF5,0xC0,0xCA,0x88,0x10,0xF0,0x60,0x20,
1075    0x3A,0x00,0x48,0x20,0x46,0xC7,0xA9,0x02,
1076    0x8D,0xF7,0xC0,0x68,0x60,0xFF,0xFF,0xFF,
1077    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1078    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1079    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
1080    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x2C,
1081    0xC6,0xC7,0x4C,0x22,0xC7,0xC1,0xC5,0x20
1082
1083]);
1084
1085
1086// Discovery container. Apple2IO mounts a separate live instance.
1087if(oEMU===undefined) var oEMU = {"component":{"IO":{"SerialPro":new SerialProCard()}}}
1088else oEMU.component.IO.SerialPro = new SerialProCard();
1089
1090
1091function SerialProCard()
1092{
1093    const bDebug = false;
1094
1095    this.id = {"PCODE":"SPC", "icon":"fa fa-terminal"};
1096
1097    /*
1098     * SPC remains the Apple II slot peripheral.
1099     *
1100     * SPGPT is a text-aware peer device that can speak either US-ASCII (GPT8)
1101     * or UTF-16LE (GPT16) over that raw serial byte stream. Declaring both here
1102     * makes device provisioning deterministic and avoids relying on constructor
1103     * wrapping in an extension script.
1104     */
1105    this.deviceConfig = [
1106        {
1107             "DCODE":"SPSERIAL"
1108            ,"hostPCODE":"SPC"
1109            ,"coID":"SerialProLine"
1110            ,"icon":"fa fa-exchange-alt"
1111            ,"description":"Serial Pro external serial line"
1112        }
1113        ,{
1114             "DCODE":"SPTERM"
1115            ,"hostPCODE":"SPC"
1116            ,"coID":"SerialProTerminalDevice"
1117            ,"icon":"fa fa-terminal"
1118            ,"description":"Serial Pro browser terminal console"
1119        }
1120        ,{
1121             "DCODE":"SPGPT"
1122            ,"hostPCODE":"SPC"
1123            ,"coID":"SerialProGPTDevice"
1124            ,"icon":"fa fa-robot"
1125            ,"description":"Serial Pro GPT8/GPT16 serial peer"
1126        }
1127    ];
1128
1129    var state =
1130    {
1131         "active":true
1132        // Keep the complete byte, not just the physical-bank bits.  Serial Pro
1133        // firmware uses selector values such as $01/$09 and $02/$0A/$12: the
1134        // low two bits choose the physical 2 KiB EPROM quarter while higher
1135        // bits remain meaningful to the software dispatcher in the selected bank.
1136        ,"romSelector":0x00
1137        ,"cpuSync":true
1138    };
1139
1140    this.state = state;
1141
1142    /*
1143     * Serial Pro clock/RAM device.
1144     *
1145     * AE documents a 6818-compatible register map behind two slot-I/O ports:
1146     *   $C084+$s0  address register (write only)
1147     *   $C085+$s0  selected register/RAM data (read/write)
1148     *
1149     * Registers $00-$0D follow the 6818 clock map; $0E-$3F are battery RAM.
1150     * The real card's firmware normally programs Register A=$20 (32.768 kHz)
1151     * and Register B=$02 (24-hour, BCD, running), so those are our power-up
1152     * defaults.  Battery RAM starts clear and is then initialized by the real
1153     * Serial Pro firmware when its $20/$21 signature is absent.
1154     *
1155     * This object survives reset()/restart() for as long as the mounted card
1156     * instance exists, modeling battery-backed RAM within an emulator session.
1157     * Browser-persistent NVRAM across page reloads is intentionally a later step.
1158     */
1159    var rtcStartMs = Date.now();
1160    var rtc =
1161    {
1162         "address":0x00
1163        ,"ram":new Uint8Array(0x40)
1164        ,"baseTimeMs":rtcStartMs
1165        ,"hostBaseMs":rtcStartMs
1166        ,"weekdayOffset":0
1167        ,"irq":false
1168        ,"nmi":false
1169        ,"lastUpdateSecond":Math.floor(rtcStartMs/1000)
1170        ,"lastIRQSampleTick":0
1171    };
1172
1173    // Minimal interrupt stage required by the resident HGR clock.
1174    // Register B: UIE=$10, SET/HOLD=$80.
1175    // Register C: UF=$10, IRQF=$80. Reading C acknowledges the RTC request.
1176    const RTC_B_UIE  = 0x10;
1177    const RTC_B_SET  = 0x80;
1178    const RTC_C_UF   = 0x10;
1179    const RTC_C_IRQF = 0x80;
1180
1181    // Diagnostic route for the Apple II+ resident-clock experiment.
1182    // The physical Serial Pro can route the 6818 interrupt to NMI.
1183    // Keep the 6551 ACIA on IRQ; only the RTC update-ended source uses NMI.
1184    const RTC_UPDATE_INTERRUPT_ROUTE = "nmi";
1185
1186    rtc.ram[0x0A] = 0x20;       // Register A: 32.768 kHz, UIP clear on read
1187    rtc.ram[0x0B] = 0x02;       // Register B: 24-hour, BCD, clock running
1188    rtc.ram[0x0D] = 0x80;       // Register D: valid time/RAM
1189
1190
1191    var serialpro = this;       // stand-in where 'this' is absent inside callbacks
1192
1193
1194    var serialLineDevice = null;
1195    var serialLineUnsubscribe = null;
1196    var serialTerminalConsoleDevice = null;
1197
1198    /*
1199     * Apple2IO.attach() calls SPSERIAL.bindHost(), which in turn binds the line
1200     * device here. Existing browser endpoints become adapters subscribed to the
1201     * line instead of being called directly from the 6551 timing code.
1202     */
1203    this.bindSerialLineDevice = function(device)
1204    {
1205        if(serialLineUnsubscribe)
1206        {
1207            serialLineUnsubscribe();
1208            serialLineUnsubscribe = null;
1209        }
1210
1211        serialLineDevice = device || null;
1212
1213        if(serialLineDevice &&
1214           typeof(serialLineDevice.subscribe)=="function")
1215        {
1216            serialLineUnsubscribe =
1217                serialLineDevice.subscribe(function(bytes,meta)
1218                {
1219                    for(var i=0;i<bytes.length;i++)
1220                    {
1221                        var d8 = bytes[i] & 0xFF;
1222
1223                        if(typeof(serialpro.serialTerminalWriteByte)=="function")
1224                            serialpro.serialTerminalWriteByte(d8);
1225
1226                        serialPhysicalWriteByte(d8);
1227                    }
1228                });
1229        }
1230
1231        return !!serialLineDevice;
1232    };
1233
1234     /*
1235     * Child peer devices such as SPGPT may attach after SPSERIAL and need the
1236     * live external-line device. Keep the actual 6551 state private to SPC;
1237     * only expose the device boundary.
1238     */
1239    this.getSerialLineDevice = function()
1240    {
1241        return serialLineDevice;
1242    };
1243
1244    this.bindSerialTerminalConsoleDevice = function(device)
1245    {
1246        serialTerminalConsoleDevice = device || null;
1247        return !!serialTerminalConsoleDevice;
1248    };
1249
1250    this.getSerialTerminalConsoleDevice = function()
1251    {
1252        return serialTerminalConsoleDevice;
1253    };
1254
1255    /*
1256     * Remote/external bytes arrive here only through SPSERIAL. Keep receive
1257     * framing/timing inside the card: this queue is the simulated line feeding
1258     * the 6551 receive-shift register.
1259     */
1260    this.serialLineReceiveBytes = function(bytes,meta)
1261    {
1262        if(!bytes || !bytes.length)
1263            return 0;
1264
1265        aciaSyncNow();
1266
1267        for(var i=0;i<bytes.length;i++)
1268            serialTerminalState.toCard.push(bytes[i] & 0xFF);
1269
1270        aciaPrimeReceiver();
1271        return bytes.length;
1272    };
1273
1274    /*
1275     * Start a new application-level serial session from a deterministic RX
1276     * boundary. A normal Apple II/6551 reset intentionally preserves remote
1277     * bytes already on the simulated line; changing GPT8/GPT16 encoding must
1278     * not preserve those bytes because they belong to the previous protocol.
1279     */
1280    this.serialLineResetReceiveSession = function()
1281    {
1282        aciaSyncNow();
1283
1284        var info = {
1285             queued:serialTerminalState.toCard.length
1286            ,rxFull:!!acia.rxFull
1287            ,rxBusy:!!acia.rxBusy
1288            ,rxData:acia.rxData & 0xFF
1289            ,rxShift:acia.rxShift & 0xFF
1290        };
1291
1292        serialTerminalState.toCard.length = 0;
1293        acia.rxData = 0x00;
1294        acia.rxFull = false;
1295        acia.rxErrors = 0x00;
1296        acia.rxShift = 0x00;
1297        acia.rxBusy = false;
1298        acia.rxTicks = 0;
1299
1300        return info;
1301    };
1302
1303    this.serialLineReceiveInfo = function()
1304    {
1305        return {
1306             queued:serialTerminalState.toCard.length
1307            ,rxFull:!!acia.rxFull
1308            ,rxBusy:!!acia.rxBusy
1309            ,rxData:acia.rxData & 0xFF
1310            ,rxShift:acia.rxShift & 0xFF
1311        };
1312    };
1313
1314    function serialLineInjectBytes(bytes,meta)
1315    {
1316        if(serialLineDevice &&
1317           typeof(serialLineDevice.receiveBytes)=="function")
1318            return serialLineDevice.receiveBytes(bytes,meta || {});
1319
1320        // Defensive bootstrap fallback before attached devices are provisioned.
1321        return serialpro.serialLineReceiveBytes(bytes,meta || {});
1322    }
1323
1324    function serialLineTransmitByte(d8)
1325    {
1326        d8 &= 0xFF;
1327
1328        if(serialLineDevice &&
1329           typeof(serialLineDevice.transmitBytes)=="function")
1330        {
1331            return serialLineDevice.transmitBytes(
1332                new Uint8Array([d8]),
1333                {"source":"acia"}
1334            );
1335        }
1336
1337        /*
1338         * Defensive fallback for tools that construct SerialProCard without
1339         * running Apple2IO.provisionPeripheral().
1340         */
1341        if(typeof(serialpro.serialTerminalWriteByte)=="function")
1342            serialpro.serialTerminalWriteByte(d8);
1343        serialPhysicalWriteByte(d8);
1344        return 1;
1345    }
1346
1347    /*
1348     * Stage 2D: legacy 6551 ACIA.
1349     *
1350     * Serial Pro firmware polls the legacy 6551 TDRE bit before transmitting,
1351     * therefore model the classic 6551/W65C51S register semantics rather than
1352     * the later W65C51N transmitter behavior.
1353     *
1354     * Register offsets within SlotIO:
1355     *   $08  data       read RX / write TX
1356     *   $09  status     read / programmed reset on write
1357     *   $0A  command    read/write
1358     *   $0B  control    read/write
1359     *
1360     * Stage 2D interprets Command/Control, applies the selected data word length,
1361     * exposes modem-control state and drives the shared Apple II IRQ line for
1362     * enabled RX, TX and DCD/DSR-change conditions.
1363     *
1364     * Character transfers are paced from the emulator's completed 6502-cycle
1365     * counter.  With CPU sync enabled (default), turbo CPU speed accelerates the
1366     * UART by the same factor.  With CPU sync disabled, completed CPU cycles are
1367     * normalized by baseClock/targetClock so the selected baud remains close to
1368     * real-world x1 timing without using Date.now()/performance.now().  We pace
1369     * complete serial frames rather than individual TXD/RXD waveform edges.
1370     */
1371    const ACIA_IRQ_RX    = 0x01;
1372    const ACIA_IRQ_TX    = 0x02;
1373    const ACIA_IRQ_MODEM = 0x04;
1374    const ACIA_BAUD_TABLE =
1375        [null,50,75,109.92,134.58,150,300,600,1200,1800,2400,3600,4800,7200,9600,19200];
1376
1377    var acia =
1378    {
1379         "command":0x00
1380        ,"control":0x00
1381        ,"rxData":0x00
1382        ,"rxFull":false
1383        ,"rxErrors":0x00
1384        ,"rxShift":0x00
1385        ,"rxBusy":false
1386        ,"rxTicks":0
1387        ,"txData":0x00
1388        ,"txEmpty":true
1389        ,"txBusy":false
1390        ,"txTicks":0
1391        ,"irq":false
1392        ,"irqReasons":0x00
1393        ,"lastClockTick":0
1394        ,"dsrReady":true
1395        ,"dcdDetected":true
1396        ,"ctsClear":true
1397        ,"dsrStatusReady":true
1398        ,"dcdStatusDetected":true
1399        ,"modemStatusLatched":false
1400    };
1401
1402    function serialProIRQSourceID()
1403    {
1404        var slot = serialpro.mount ? Number(serialpro.mount.slotN)-1 : "?";
1405        return "SerialPro:"+slot;
1406    }
1407
1408    // The physical card ORs its interrupt-capable devices onto the selected
1409    // Apple II interrupt line.  Keep one emulator IRQ source asserted while
1410    // either the 6551 ACIA or the 6818 RTC still has a pending request.
1411    function serialProDriveIRQ()
1412     {
1413        var asserted = !!(acia.irq || rtc.irq);
1414        if(typeof(apple2plus)!="object" || !apple2plus ||
1415           typeof(apple2plus.hwObj)!="function")
1416            return false;
1417
1418        var hw = apple2plus.hwObj();
1419        if(!hw) return false;
1420
1421        if(typeof(hw.setIRQSource)=="function")
1422            hw.setIRQSource(serialProIRQSourceID(),asserted);
1423        else
1424            hw.irq_signal = asserted ? 1 : 0;       // compatibility fallback
1425        return asserted;
1426    }
1427
1428    function aciaDriveIRQ()
1429    {
1430        return serialProDriveIRQ();
1431    }
1432
1433    function aciaAssertIRQ(reason)
1434    {
1435        acia.irqReasons |= reason & 0xFF;
1436        acia.irq = true;
1437        aciaDriveIRQ();
1438    }
1439
1440    function aciaClearIRQ()
1441    {
1442        acia.irqReasons = 0x00;
1443        acia.irq = false;
1444        aciaDriveIRQ();
1445    }
1446
1447    function rtcAssertIRQ()
1448    {
1449        rtc.irq = true;
1450        return serialProDriveIRQ();
1451    }
1452
1453    function rtcClearIRQ()
1454    {
1455        rtc.irq = false;
1456        return serialProDriveIRQ();
1457    }
1458
1459    function rtcDriveNMI(active)
1460    {
1461        if(typeof(apple2plus)!="object" || !apple2plus ||
1462           typeof(apple2plus.hwObj)!="function")
1463            return false;
1464
1465        var hw = apple2plus.hwObj();
1466        if(!hw) return false;
1467
1468        // EMU_cpu6502.js consumes nmi_signal when the NMI edge is accepted.
1469        // Clearing Register C before CPU acceptance also cancels the pending edge.
1470        hw.nmi_signal = active ? 1 : 0;
1471        return !!active;
1472    }
1473
1474    function rtcAssertNMI()
1475    {
1476        // Do not manufacture a new edge while the same RTC request is pending.
1477        if(rtc.nmi) return true;
1478
1479        rtc.nmi = true;
1480
1481        // RTC is routed away from the shared IRQ line. Preserve any ACIA IRQ.
1482        rtc.irq = false;
1483        serialProDriveIRQ();
1484
1485        return rtcDriveNMI(true);
1486    }
1487
1488    function rtcClearNMI()
1489    {
1490        rtc.nmi = false;
1491        rtcDriveNMI(false);
1492        return true;
1493    }
1494
1495    function rtcAssertInterrupt()
1496    {
1497        return RTC_UPDATE_INTERRUPT_ROUTE=="nmi"
1498            ? rtcAssertNMI()
1499            : rtcAssertIRQ();
1500    }
1501
1502    function rtcClearInterrupt()
1503    {
1504        return RTC_UPDATE_INTERRUPT_ROUTE=="nmi"
1505            ? rtcClearNMI()
1506            : rtcClearIRQ();
1507    }
1508
1509    /*
1510     * Keep 6551 register decoding independent from the source of the register
1511     * bytes.  The Serial Pro has two related serial states:
1512     *
1513     *   RTC battery RAM $23/$24  = saved/configured CONTROL + COMMAND
1514     *   acia.control/command     = live 6551 hardware registers
1515     *
1516     * The ROM control panel edits the saved bytes first; firmware subsequently
1517     * copies them into the live 6551 when it (re)initializes the interface.
1518     */
1519    function serialWordLengthFromControl(control)
1520    {
1521        return 8 - (((control & 0xFF) >> 5) & 0x03);
1522    }
1523    
1524
1525    function aciaWordLength()
1526    {
1527        return serialWordLengthFromControl(acia.control);
1528    }
1529
1530    function aciaDataMask()
1531    {
1532        return (0xFF >> (8-aciaWordLength())) & 0xFF;
1533    }
1534
1535    function serialParityFromCommand(command)
1536    {
1537        command &= 0xFF;
1538        if((command & 0x20)==0) return "none";
1539        return ["odd","even","mark","space"][(command >> 6) & 0x03];
1540    }
1541
1542    function aciaParity()
1543    {
1544        return serialParityFromCommand(acia.command);
1545    }
1546
1547    function aciaParityChecked()
1548    {
1549        if((acia.command & 0x20)==0) return false;
1550        return ((acia.command >> 6) & 0x03) < 2;    // odd/even only
1551    }
1552
1553    function serialStopBitsFromRegisters(control,command)
1554    {
1555        control &= 0xFF;
1556        command &= 0xFF;
1557        if((control & 0x80)==0) return 1;
1558
1559        var bits = serialWordLengthFromControl(control);
1560        var parity = serialParityFromCommand(command);
1561        if(bits==5 && parity=="none") return 1.5;
1562        if(bits==8 && parity!="none") return 1;
1563        return 2;
1564    }
1565
1566    function aciaStopBits()
1567    {
1568        return serialStopBitsFromRegisters(acia.control,acia.command);
1569    }
1570
1571    function serialBaudFromControl(control)
1572    {
1573        return ACIA_BAUD_TABLE[control & 0x0F];
1574    }
1575
1576    function aciaBaudRate()
1577    {
1578        return serialBaudFromControl(acia.control);
1579    }
1580
1581    function serialFormatSignature(control,command)
1582    {
1583        return [
1584             serialBaudFromControl(control)
1585            ,serialWordLengthFromControl(control)
1586            ,serialParityFromCommand(command)
1587            ,serialStopBitsFromRegisters(control,command)
1588            ,(control & 0x10)!=0 ? "rx=tx" : "rx=ext"
1589        ].join("/");
1590    }
1591
1592    function serialFormatLabel(control,command)
1593    {
1594        var baud = serialBaudFromControl(control);
1595        var parity = serialParityFromCommand(command);
1596        var parityLetter = {
1597             "none":"N"
1598            ,"odd":"O"
1599            ,"even":"E"
1600            ,"mark":"M"
1601            ,"space":"S"
1602        }[parity] || "?";
1603
1604        return String(baud==null ? "EXT" : baud)
1605            +" "+serialWordLengthFromControl(control)
1606            +parityLetter
1607            +String(serialStopBitsFromRegisters(control,command));
1608    }
1609
1610    function serialConfiguredState()
1611    {
1612        // Until the AE/$55 NVRAM signature exists, preserve the pre-existing
1613        // browser behavior by presenting the live ACIA state.  Once firmware
1614        // initializes the battery RAM, $23/$24 become authoritative for SER.
1615        var nvramValid = rtc.ram[0x20]==0xAE && rtc.ram[0x21]==0x55;
1616        var control = nvramValid ? (rtc.ram[0x23] & 0xFF) : (acia.control & 0xFF);
1617        var command = nvramValid ? (rtc.ram[0x24] & 0xFF) : (acia.command & 0xFF);
1618
1619        return {
1620             "valid":nvramValid
1621            ,"control":control
1622            ,"command":command
1623            ,"baud":serialBaudFromControl(control)
1624            ,"dataBits":serialWordLengthFromControl(control)
1625            ,"parity":serialParityFromCommand(command)
1626            ,"stopBits":serialStopBitsFromRegisters(control,command)
1627            ,"rxClockInternal":(control & 0x10)!=0
1628        };
1629    }
1630
1631    function aciaCPUMultiplier()
1632    {
1633        var base = typeof(_o)!="undefined" ? Number(_o.CPU_ClocksTicks_s) : NaN;
1634        var target = typeof(_o)!="undefined" ? Number(_o.CPU_TargetTicks_s) : NaN;
1635        return Number.isFinite(base) && base>0 && Number.isFinite(target) && target>0
1636            ? target/base
1637            : 1;
1638    }
1639
1640    function aciaEffectiveBaudRate()
1641    {
1642        var baud = aciaBaudRate();
1643        if(!Number.isFinite(baud)) return baud;
1644        return state.cpuSync ? baud*aciaCPUMultiplier() : baud;
1645    }
1646
1647    function aciaFrameBits()
1648    {
1649        return 1                                  // start bit
1650            + aciaWordLength()
1651            + (aciaParity()=="none" ? 0 : 1)
1652            + aciaStopBits();
1653    }
1654
1655    function aciaFrameTicks()
1656    {
1657        var baud = aciaBaudRate();
1658        var base = typeof(_o)!="undefined" ? Number(_o.CPU_ClocksTicks_s) : NaN;
1659        if(!Number.isFinite(baud) || baud<=0 || !Number.isFinite(base) || base<=0)
1660            return Infinity;
1661        return base*aciaFrameBits()/baud;
1662    }
1663
1664    function aciaClockScale()
1665    {
1666        if(state.cpuSync) return 1;
1667        var multiplier = aciaCPUMultiplier();
1668        return multiplier>0 ? 1/multiplier : 1;
1669    }
1670
1671    function aciaClockNow(ctx)
1672    {
1673        if(ctx && ctx.io && typeof(ctx.io.getClockTicks)=="function")
1674            return Number(ctx.io.getClockTicks());
1675
1676        if(typeof(apple2plus)=="object" && apple2plus &&
1677           typeof(apple2plus.hwObj)=="function")
1678        {
1679            var hw = apple2plus.hwObj();
1680            if(hw && hw.io && typeof(hw.io.getClockTicks)=="function")
1681                return Number(hw.io.getClockTicks());
1682        }
1683        return Number(acia.lastClockTick) || 0;
1684    }
1685
1686    function aciaAdvanceTiming(serialTicks)
1687    {
1688        var frameTicks = aciaFrameTicks();
1689        if(!Number.isFinite(frameTicks) || frameTicks<=0 ||
1690           !Number.isFinite(serialTicks) || serialTicks<=0)
1691            return false;
1692
1693        if(acia.txBusy)
1694        {
1695            acia.txTicks += serialTicks;
1696            if(acia.txTicks>=frameTicks)
1697            {
1698                var txByte = acia.txData & aciaDataMask();
1699                acia.txTicks = 0;
1700                acia.txBusy = false;
1701
1702                if(!aciaBreakActive())
1703                    serialLineTransmitByte(txByte);
1704
1705
1706                acia.txEmpty = true;
1707                if(aciaTransmitIRQEnabled())
1708                    aciaAssertIRQ(ACIA_IRQ_TX);
1709            }
1710        }
1711
1712        if(acia.rxBusy)
1713        {
1714            acia.rxTicks += serialTicks;
1715            if(acia.rxTicks>=frameTicks)
1716            {
1717                acia.rxTicks = 0;
1718                acia.rxBusy = false;
1719                acia.rxData = acia.rxShift & aciaDataMask();
1720                acia.rxFull = true;
1721                if(aciaReceiverIRQEnabled())
1722                    aciaAssertIRQ(ACIA_IRQ_RX);
1723            }
1724        }
1725        return true;
1726    }
1727
1728    function aciaSyncClock(clockTick)
1729    {
1730        clockTick = Number(clockTick);
1731        if(!Number.isFinite(clockTick)) return false;
1732
1733        var previous = Number(acia.lastClockTick);
1734        acia.lastClockTick = clockTick;
1735        if(!Number.isFinite(previous) || clockTick<=previous) return true;
1736
1737        return aciaAdvanceTiming((clockTick-previous)*aciaClockScale());
1738    }
1739
1740    function aciaSyncNow(ctx)
1741    {
1742        return aciaSyncClock(aciaClockNow(ctx));
1743    }
1744
1745    function aciaDTRAsserted()
1746    {
1747        return (acia.command & 0x01)!=0;             // DTRB low
1748    }
1749
1750    function aciaReceiverIRQEnabled()
1751    {
1752        return aciaDTRAsserted() && (acia.command & 0x02)==0;
1753    }
1754
1755    function aciaTransmitMode()
1756    {
1757        return (acia.command >> 2) & 0x03;
1758    }
1759
1760    function aciaEchoMode()
1761    {
1762        return (acia.command & 0x10)!=0;
1763    }
1764
1765    function aciaRTSAsserted()
1766    {
1767        if(aciaEchoMode()) return true;              // echo mode forces RTSB low
1768        return aciaTransmitMode()!=0;                // TIC 00 is RTSB high
1769    }
1770
1771    function aciaTransmitIRQEnabled()
1772    {
1773        return aciaDTRAsserted() && !aciaEchoMode() && aciaTransmitMode()==1;
1774    }
1775
1776    function aciaBreakActive()
1777    {
1778        return !aciaEchoMode() && aciaTransmitMode()==3;
1779    }
1780
1781    function aciaEvaluateEnabledInterrupts()
1782    {
1783        if(aciaReceiverIRQEnabled())
1784        {
1785            if(acia.rxFull)
1786                aciaAssertIRQ(ACIA_IRQ_RX);
1787            if(acia.modemStatusLatched)
1788                aciaAssertIRQ(ACIA_IRQ_MODEM);
1789        }
1790
1791        if(aciaTransmitIRQEnabled() && acia.txEmpty)
1792            aciaAssertIRQ(ACIA_IRQ_TX);
1793    }
1794
1795    function aciaHardwareReset()
1796    {
1797        acia.command = 0x00;
1798        acia.control = 0x00;
1799        acia.rxData = 0x00;
1800        acia.rxFull = false;
1801        acia.rxErrors = 0x00;
1802        acia.rxShift = 0x00;
1803        acia.rxBusy = false;
1804        acia.rxTicks = 0;
1805        acia.txData = 0x00;
1806        acia.txEmpty = true;
1807        acia.txBusy = false;
1808        acia.txTicks = 0;
1809        acia.lastClockTick = aciaClockNow();
1810        acia.dsrStatusReady = acia.dsrReady;
1811        acia.dcdStatusDetected = acia.dcdDetected;
1812        acia.modemStatusLatched = false;
1813        aciaClearIRQ();
1814
1815        // A browser serial port stays physically open across Apple II RESET.
1816        serialPhysicalCheckConfiguration();
1817        serialPhysicalSyncOutputs();     
1818    }
1819
1820    function aciaProgramReset()
1821    {
1822        // Legacy 6551 programmed reset: clear Command bits 4..0 and Status
1823        // overrun.  Parity selection bits 7..5 and Control are unchanged.
1824        acia.command &= 0xE0;
1825        acia.rxErrors &= ~0x04;
1826        aciaClearIRQ();
1827        serialPhysicalCheckConfiguration();
1828        serialPhysicalSyncOutputs();
1829    }
1830
1831    function aciaWriteCommand(value)
1832    {
1833        acia.command = value & 0xFF;
1834
1835        // Leaving a blocked condition may start a waiting transmitter frame.
1836        aciaTryTransmit();
1837        aciaEvaluateEnabledInterrupts();
1838        serialPhysicalCheckConfiguration();
1839        serialPhysicalSyncOutputs();
1840    }
1841
1842    function aciaWriteControl(value)
1843    {
1844        acia.control = value & 0xFF;
1845        aciaTryTransmit();
1846        aciaPrimeReceiver();
1847        serialPhysicalCheckConfiguration();
1848    }
1849
1850    /*
1851     * Receiver bridge. serialTerminalState.toCard[] is the remote-end byte
1852     * stream waiting on the simulated line.  A byte first occupies a simple
1853     * receive-shift stage for one configured frame time; only then does RDRF
1854     * become visible to the CPU.
1855     *
1856     * This stage deliberately retains the existing one-byte receive holding
1857     * model: the next queued frame starts after DATA is consumed.  Detailed
1858     * overrun behavior remains a later refinement.
1859     */
1860    function aciaPrimeReceiver()
1861    {
1862        if(acia.rxFull || acia.rxBusy) return true;
1863        if(!serialTerminalState || !serialTerminalState.toCard) return false;
1864        if(!serialTerminalState.toCard.length) return false;
1865        if((acia.control & 0x10)==0)  return false;     // external receiver clock
1866        if(!Number.isFinite(aciaBaudRate())) return false;
1867
1868        acia.rxShift = serialTerminalState.toCard.shift() & aciaDataMask();
1869        acia.rxTicks = 0;
1870        acia.rxBusy = true;       
1871        return true;
1872    }
1873
1874    function aciaRXPending()
1875    {
1876        return !!acia.rxFull;
1877    }
1878
1879    function aciaReadData(ctx)
1880    {
1881        if(!aciaRXPending())
1882            return acia.rxData & 0xFF;
1883
1884        // Safe debugger/memory inspection must not consume the receive register.
1885        if(ctx && ctx.bRO===true)
1886            return acia.rxData & 0xFF;
1887
1888        var value = acia.rxData & 0xFF;
1889        acia.rxFull = false;        
1890        acia.rxErrors = 0x00;       // PE/FE/OVRN clear after DATA read
1891        // Start, but do not instantly complete, the next queued remote frame.
1892        aciaPrimeReceiver();
1893        return value;
1894    }
1895
1896    function aciaReadStatus(ctx)
1897    {
1898        var status = acia.rxErrors & 0x07;
1899
1900        if(acia.rxFull)           status |= 0x08;  // RDRF
1901        if(acia.txEmpty)          status |= 0x10;  // TDRE
1902        if(!acia.dcdStatusDetected) status |= 0x20;// DCD high/not detected
1903        if(!acia.dsrStatusReady)    status |= 0x40;// DSR high/not ready
1904        if(acia.irq)              status |= 0x80;
1905
1906        if(!(ctx && ctx.bRO===true))
1907        {
1908            var hadModemLatch = acia.modemStatusLatched;
1909            aciaClearIRQ();                         // Status read clears IRQ bit
1910
1911            /*
1912             * DCD/DSR status is latched on the first transition.  If either
1913             * physical input changed again before Status was read, expose the
1914             * new level now and immediately generate the next modem interrupt.
1915             */
1916            if(hadModemLatch)
1917            {
1918                acia.modemStatusLatched = false;
1919
1920                if(acia.dcdStatusDetected!=acia.dcdDetected ||
1921                   acia.dsrStatusReady!=acia.dsrReady)
1922                {
1923                    acia.dcdStatusDetected = acia.dcdDetected;
1924                    acia.dsrStatusReady = acia.dsrReady;
1925                    acia.modemStatusLatched = true;
1926
1927                    if(aciaReceiverIRQEnabled())
1928                        aciaAssertIRQ(ACIA_IRQ_MODEM);
1929                }
1930            }
1931        }
1932
1933        return status & 0xFF;
1934    }
1935
1936    function aciaTryTransmit()
1937    {
1938        if(acia.txEmpty || acia.txBusy || !acia.ctsClear)
1939            return false;
1940        if(!Number.isFinite(aciaBaudRate()))
1941            return false;
1942
1943        acia.txTicks = 0;
1944        acia.txBusy = true;
1945        return true;
1946    }
1947
1948    function aciaWriteData(d8)
1949    {
1950        acia.txData = d8 & 0xFF;
1951        acia.txEmpty = false;
1952        aciaTryTransmit();
1953    }
1954    function aciaLatchModemStatusChange()
1955    {
1956        if(acia.modemStatusLatched)
1957            return;
1958
1959        acia.dcdStatusDetected = acia.dcdDetected;
1960        acia.dsrStatusReady = acia.dsrReady;
1961        acia.modemStatusLatched = true;
1962
1963        if(aciaReceiverIRQEnabled())
1964            aciaAssertIRQ(ACIA_IRQ_MODEM);
1965    }
1966
1967    function aciaDescribe()
1968    {
1969        var baud = aciaBaudRate();
1970        var receiverInternal = (acia.control & 0x10)!=0;
1971
1972        return {
1973             "command":acia.command & 0xFF
1974            ,"control":acia.control & 0xFF
1975            ,"status":aciaReadStatus({"bRO":true})
1976            ,"wordLength":aciaWordLength()
1977            ,"dataMask":aciaDataMask()
1978            ,"parity":aciaParity()
1979            ,"parityCheck":aciaParityChecked()
1980            ,"stopBits":aciaStopBits()
1981            ,"txBaud":baud
1982            ,"effectiveTxBaud":aciaEffectiveBaudRate()
1983            ,"cpuSync":state.cpuSync
1984            ,"cpuMultiplier":aciaCPUMultiplier()
1985            ,"txClock":baud==null ? "external/16" : "internal"
1986            ,"rxBaud":receiverInternal ? baud : null
1987            ,"rxClock":receiverInternal ? "transmit-rate" : "external/16"
1988            ,"txBusy":acia.txBusy
1989            ,"rxBusy":acia.rxBusy
1990            ,"dtr":aciaDTRAsserted()
1991            ,"rts":aciaRTSAsserted()
1992            ,"cts":acia.ctsClear
1993            ,"dcd":acia.dcdDetected
1994            ,"dsr":acia.dsrReady
1995            ,"echo":aciaEchoMode()
1996            ,"break":aciaBreakActive()
1997            ,"rxIRQEnabled":aciaReceiverIRQEnabled()
1998            ,"txIRQEnabled":aciaTransmitIRQEnabled()
1999            ,"irq":acia.irq
2000            ,"irqReasons":acia.irqReasons & 0xFF
2001            ,"rxFull":acia.rxFull
2002            ,"txEmpty":acia.txEmpty
2003        };
2004
2005    }
2006
2007
2008    this.action =
2009    {
2010        "SlotROM":
2011        {
2012            "RD":{"callback":function(addr,ctx) {
2013                return serialpro.readROM(addr,ctx);
2014            }}
2015        },
2016        "SlotIO":
2017        {
2018            "RD":{"callback":function(addr,ctx) {
2019                return serialpro.readSlotIO(addr,ctx);
2020            }},
2021            "WR":{"callback":function(addr,d8,ctx) {
2022                return serialpro.writeSlotIO(addr,d8,ctx);
2023            }}
2024        },
2025        "HostROM":
2026        {
2027            "RD":{"callback":function(addr,ctx) {
2028                return serialpro.readHostROM(addr,ctx);
2029            }}
2030        }
2031    };
2032
2033
2034    function ownsExpansionROM()
2035    {
2036        if(!serialpro.mount) return false;
2037        return oEMU.component.IO.ACTION_MAP.Hslot == (serialpro.mount.slotN-1);
2038    }
2039
2040
2041    function selectExpansionROM(ctx)
2042    {
2043        // Safe/debugger ROM inspection must not alter shared C8 ownership.
2044        if(ctx && ctx.bRO===true) return false;
2045        if(!serialpro.mount || !serialpro.mount.ranges || !serialpro.mount.ranges.HostROM)
2046            return false;
2047
2048        var CIO = oEMU.component.IO;
2049        if(!CIO.ACTION_MAP) return false;
2050
2051        if(CIO.ACTION_MAP.Hslot == (serialpro.mount.slotN-1))
2052            return true;
2053
2054        CIO.ACTION_MAP.Hslot = serialpro.mount.slotN-1;
2055
2056        /*
2057         * Apple II expansion-ROM selection is independent from the Serial Pro's
2058         * own EPROM bank register.  Reading this card's $Cnxx page claims the
2059         * shared $C800-$CFFF window for this slot; BANKCLR/BANKREG merely choose
2060         * which 2 KiB quarter of the card's 27C64 appears in that window.
2061         */
2062        var range = serialpro.mount.ranges.HostROM;
2063        for(var i=range.from;i<=range.to;i++)
2064            CIO.ACTION_MAP.RD[i] = serialpro.action.HostROM.RD.callback;
2065
2066        if(bDebug)
2067            console.log("Serial Pro claims $C800-$CFFF from PR#"+(serialpro.mount.slotN-1));
2068
2069        return true;
2070    }
2071
2072
2073    function releaseExpansionROM()
2074    {
2075        if(!serialpro.mount || !oEMU.component.IO.ACTION_MAP) return false;
2076
2077        var CIO = oEMU.component.IO;
2078        if(CIO.ACTION_MAP.Hslot != (serialpro.mount.slotN-1))
2079            return false;
2080
2081        CIO.ACTION_MAP.Hslot = null;
2082        return true;
2083    }
2084
2085    function physicalHostROMBank()
2086    {
2087        /*
2088         * Reverse-engineered from the complete v2.0 EPROM and its dispatcher:
2089         *
2090         *     selector .... ..00 -> EPROM $0000-$07FF
2091         *     selector .... ..01 -> EPROM $0800-$0FFF
2092         *     selector .... ..10 -> EPROM $1000-$17FF
2093         *     selector .... ..11 -> EPROM $1800-$1FFF
2094         *
2095         * AE documents the two write-only switches, but not this bit-level
2096         * encoding.  The firmware proves the low-two-bit relationship: e.g.
2097         * $01 and $09 use physical bank 1, while $02/$0A/$12 use bank 2.
2098         */
2099        return state.romSelector & 0x03;
2100    }    
2101
2102    function rtcIsBinary()
2103    {
2104        return (rtc.ram[0x0B] & 0x04) != 0;
2105    }
2106
2107    function rtcIs24Hour()
2108    {
2109        return (rtc.ram[0x0B] & 0x02) != 0;
2110    }
2111
2112    function rtcToBCD(value)
2113    {
2114        value = Math.max(0,Math.floor(value));
2115        return (((Math.floor(value/10) % 10) << 4) | (value % 10)) & 0xFF;
2116    }
2117
2118    function rtcFromBCD(value)
2119    {
2120        return (((value >> 4) & 0x0F) * 10) + (value & 0x0F);
2121    }
2122
2123    function rtcEncode(value)
2124    {
2125        return rtcIsBinary() ? (value & 0xFF) : rtcToBCD(value);
2126    }
2127
2128    function rtcDecode(value)
2129    {
2130        return rtcIsBinary() ? (value & 0xFF) : rtcFromBCD(value);
2131    }
2132
2133    function rtcCurrentDate()
2134    {
2135        var timeMs = rtc.baseTimeMs;
2136
2137        // Register B bit 7 is SET/HOLD.  While clear the clock advances at
2138        // wall-clock speed; while set it remains frozen for coherent updates.
2139        if((rtc.ram[0x0B] & 0x80)==0)
2140            timeMs += Date.now() - rtc.hostBaseMs;
2141
2142        return new Date(timeMs);
2143    }
2144
2145    function rtcRebase(date)
2146    {
2147        rtc.baseTimeMs = date.getTime();
2148        rtc.hostBaseMs = Date.now();
2149
2150        // A programmed clock jump must not be mistaken for a natural
2151        // update-ended boundary on the next peripheral timing sample.
2152        rtc.lastUpdateSecond = Math.floor(rtc.baseTimeMs/1000);
2153    }
2154
2155    function rtcUpdateEndedPoll()
2156    {
2157        var second = Math.floor(rtcCurrentDate().getTime()/1000);
2158
2159        // SET/HOLD suppresses time updates. Keep the detector aligned with the
2160        // frozen image so releasing SET waits for the next real second edge.
2161        if((rtc.ram[0x0B] & RTC_B_SET)!=0)
2162        {
2163            rtc.lastUpdateSecond = second;
2164            return false;
2165        }
2166
2167        if(!Number.isFinite(rtc.lastUpdateSecond))
2168        {
2169            rtc.lastUpdateSecond = second;
2170            return false;
2171        }
2172
2173        if(second==rtc.lastUpdateSecond)
2174            return false;
2175
2176        // A delayed emulator slice can cross more than one wall-clock second.
2177        // Register C is level/flag based, so one pending UF is sufficient.
2178        rtc.lastUpdateSecond = second;
2179        rtc.ram[0x0C] |= RTC_C_UF;
2180
2181        if((rtc.ram[0x0B] & RTC_B_UIE)!=0)
2182        {
2183            rtc.ram[0x0C] |= RTC_C_IRQF;
2184            rtcAssertInterrupt();
2185        }
2186        return true;
2187    }
2188
2189    function rtcIRQSampleTicks()
2190    {
2191        var base = typeof(_o)!="undefined" ? Number(_o.CPU_ClocksTicks_s) : NaN;
2192        return Number.isFinite(base) && base>0
2193            ? Math.max(1,Math.floor(base/32))       // about 32 polls/sec at x1
2194            : 32768;
2195    }
2196
2197    function rtcSyncClock(clockTick)
2198    {
2199        clockTick = Number(clockTick);
2200        if(!Number.isFinite(clockTick)) return false;
2201
2202        var previous = Number(rtc.lastIRQSampleTick);
2203
2204        // CPU RESET/restart can move the completed-cycle counter backwards.
2205        if(!Number.isFinite(previous) || clockTick<previous)
2206        {
2207            rtc.lastIRQSampleTick = clockTick;
2208            rtc.lastUpdateSecond = Math.floor(rtcCurrentDate().getTime()/1000);
2209            return true;
2210        }
2211
2212        if(clockTick-previous < rtcIRQSampleTicks())
2213            return true;
2214
2215        rtc.lastIRQSampleTick = clockTick;
2216        rtcUpdateEndedPoll();
2217        return true;
2218    }
2219
2220    function rtcEncodeHour(date)
2221    {
2222        var hour = date.getHours();
2223
2224        if(rtcIs24Hour())
2225            return rtcEncode(hour);
2226
2227        var pm = hour >= 12;
2228        hour %= 12;
2229        if(hour==0) hour = 12;
2230        return rtcEncode(hour) | (pm ? 0x80 : 0x00);
2231    }
2232
2233    function rtcDecodeHour(value)
2234    {
2235        if(rtcIs24Hour())
2236            return Math.min(23,rtcDecode(value & 0x7F));
2237
2238        var pm = (value & 0x80) != 0;
2239        var hour = rtcDecode(value & 0x7F);
2240        if(hour<1) hour = 1;
2241        if(hour>12) hour = 12;
2242        if(hour==12) hour = 0;
2243        return hour + (pm ? 12 : 0);
2244    }
2245
2246    function rtcReadData(ctx)
2247    {
2248        var reg = rtc.address & 0x3F;
2249        var date = rtcCurrentDate();
2250
2251        switch(reg)
2252        {
2253            case 0x00: return rtcEncode(date.getSeconds());
2254            case 0x01: return rtc.ram[reg];                 // seconds alarm
2255            case 0x02: return rtcEncode(date.getMinutes());
2256            case 0x03: return rtc.ram[reg];                 // minutes alarm
2257            case 0x04: return rtcEncodeHour(date);
2258            case 0x05: return rtc.ram[reg];                 // hours alarm
2259            case 0x06:
2260            {
2261                // 6818 day-of-week range is 1..7.  JavaScript uses Sunday=0.
2262                var weekday = ((date.getDay() + rtc.weekdayOffset) % 7) + 1;
2263                return rtcEncode(weekday);
2264            }
2265            case 0x07: return rtcEncode(date.getDate());
2266            case 0x08: return rtcEncode(date.getMonth()+1);
2267            case 0x09: return rtcEncode(date.getFullYear()%100);
2268
2269            case 0x0A:
2270                /*
2271                 * Register A bit 7 is Update-In-Progress.  RetroAppleJS does not
2272                 * model the 1.984 ms ripple/update interval cycle-accurately; a
2273                 * JavaScript Date snapshot is atomic for our purposes, so UIP is
2274                 * deliberately reported clear.  This is sufficient for the AE
2275                 * firmware's $CsA9 safe-clock-image loop.
2276                 */
2277                return rtc.ram[0x0A] & 0x7F;
2278
2279            case 0x0B:
2280                return rtc.ram[0x0B];
2281
2282            case 0x0C:
2283            {
2284                // Register C reports the latched interrupt flags. A real CPU read
2285                // acknowledges all RTC sources and releases the RTC IRQ request.
2286                // Debugger/read-only inspection must remain side-effect free.
2287                var flags = rtc.ram[0x0C] & 0xF0;
2288                if(!(ctx && ctx.bRO===true))
2289                {
2290                    rtc.ram[0x0C] = 0x00;
2291                    rtcClearInterrupt();
2292                }
2293                return flags;
2294            }
2295
2296            case 0x0D:
2297                return 0x80;                                // valid time/RAM
2298        }
2299
2300        // $0E-$3F are battery-backed RAM and are not affected by RTC updates.
2301        return rtc.ram[reg] & 0xFF;
2302    }
2303
2304    function rtcWriteData(value)
2305    {
2306        var reg = rtc.address & 0x3F;
2307        value &= 0xFF;
2308
2309        if(reg==0x01 || reg==0x03 || reg==0x05)
2310        {
2311            rtc.ram[reg] = value;                           // alarm registers
2312            return;
2313        }
2314
2315        if(reg==0x0A)
2316        {
2317            rtc.ram[0x0A] = value & 0x7F;                  // UIP is read only
2318            return;
2319        }
2320
2321        if(reg==0x0B)
2322        {
2323            var wasHeld = (rtc.ram[0x0B] & 0x80) != 0;
2324            var willHold = (value & 0x80) != 0;
2325
2326            // Freeze the exact current emulated clock before asserting SET.
2327            if(!wasHeld && willHold)
2328                rtcRebase(rtcCurrentDate());
2329
2330            rtc.ram[0x0B] = value;
2331
2332            // Clearing SET restarts elapsed-time accumulation from the frozen
2333            // value rather than jumping by the amount of time spent halted.
2334            if(wasHeld && !willHold)
2335            {
2336                rtc.hostBaseMs = Date.now();
2337                rtc.lastUpdateSecond = Math.floor(rtc.baseTimeMs/1000);
2338            }
2339            return;
2340        }
2341
2342        if(reg==0x0C || reg==0x0D)
2343            return;                                         // read-only registers
2344
2345        if(reg>=0x0E)
2346        {
2347            rtc.ram[reg] = value;                           // battery RAM
2348
2349            // The ROM control panel stores the saved 6551 configuration in
2350            // battery RAM $23/$24.  Defer DOM work to the dashboard refresh
2351            // sequencer; merely flag that the configured SER controls changed.
2352            if(serialUI && (reg==0x23 || reg==0x24))
2353                serialUI.configDirty = true;            
2354            return;
2355        }
2356
2357        var date = rtcCurrentDate();
2358        var decoded = rtcDecode(value);
2359
2360        switch(reg)
2361        {
2362            case 0x00:
2363                date.setSeconds(Math.max(0,Math.min(59,decoded)),0);
2364                break;
2365            case 0x02:
2366                date.setMinutes(Math.max(0,Math.min(59,decoded)));
2367                break;
2368            case 0x04:
2369                date.setHours(rtcDecodeHour(value));
2370                break;
2371            case 0x06:
2372            {
2373                // The DOW counter is independently writable on the 6818.  Keep
2374                // an offset from JavaScript's calendar weekday so it advances
2375                // naturally while still honoring a value explicitly written.
2376                var requested = Math.max(1,Math.min(7,decoded));
2377                var actual = date.getDay()+1;
2378                rtc.weekdayOffset = (requested - actual + 7) % 7;
2379                return;
2380            }
2381            case 0x07:
2382                date.setDate(Math.max(1,Math.min(31,decoded)));
2383                break;
2384            case 0x08:
2385                date.setMonth(Math.max(1,Math.min(12,decoded))-1);
2386                break;
2387            case 0x09:
2388            {
2389                var year = Math.max(0,Math.min(99,decoded));
2390                var century = Math.floor(date.getFullYear()/100)*100;
2391                date.setFullYear(century + year);
2392                break;
2393            }
2394        }
2395
2396        rtcRebase(date);
2397    }
2398
2399    this.readSlotIO = function(addr,ctx)
2400    {
2401        addr &= 0x0F;
2402
2403        if(addr>=0x08 && addr<=0x0B)
2404            aciaSyncNow(ctx);
2405
2406        switch(addr)
2407        {
2408            case 0x05:      // $C085+$s0: selected 6818 Clock/RAM data
2409                return rtcReadData(ctx);
2410            case 0x08:      // $C088+$s0: 6551 Receiver Data Register
2411                return aciaReadData(ctx);
2412
2413            case 0x09:      // $C089+$s0: 6551 Status Register
2414                return aciaReadStatus(ctx);
2415
2416            case 0x0A:      // $C08A+$s0: 6551 Command Register
2417                return acia.command & 0xFF;
2418
2419            case 0x0B:      // $C08B+$s0: 6551 Control Register
2420                return acia.control & 0xFF;
2421        }
2422
2423        // $x4, $x6 and $x7 are write-only.
2424        return 0x00;
2425    };
2426
2427    this.readROM = function(addr,ctx)
2428    {
2429        /*
2430         * $Cn00-$CnFF slot-page view.
2431         *
2432         * The 27C64 does not contain one generic SlotROM.  Its final seven
2433         * 256-byte pages are pre-assembled for slots 1..7:
2434         *
2435         *     slot 1 -> EPROM $1900
2436         *     slot 2 -> EPROM $1A00
2437         *       ...
2438         *     slot 7 -> EPROM $1F00
2439         *
2440         * Therefore a live card moved to another slot automatically exposes
2441         * the authentic slot-specific bytes; no run-time patching is needed.
2442         */
2443        selectExpansionROM(ctx);
2444
2445        addr &= 0xFF;
2446        if(!serialpro.mount) return 0xFF;
2447
2448        /*
2449         * RetroAppleJS stores peripheral slots with an internal +1 offset:
2450         *   Apple slot 2 -> mount.slotN == 3
2451         * (slotID2n()/slot2ID() in EMU_apple2io.js).
2452         * Convert back to the real Apple slot number before selecting the
2453         * corresponding pre-assembled SlotROM page in the 27C64.
2454         */
2455        var slotN = Number(serialpro.mount.slotN) - 1;
2456        if(!Number.isInteger(slotN) || slotN<1 || slotN>7) return 0xFF;
2457
2458        var eprom_addr = 0x1800 + (slotN << 8) + addr;
2459        return SERIALPRO_EPROM[eprom_addr] & 0xFF;
2460    };
2461
2462
2463    this.writeSlotIO = function(addr,d8,ctx)
2464    {
2465        /*
2466         * AE Serial Pro Programmer's Reference register map:
2467         *
2468         *   $C084+$s0  6818 Clock/RAM Address       (write only)
2469         *   $C085+$s0  6818 Clock/RAM Data          (read/write)
2470         *   $C086+$s0  EPROM Bank Register Clear    (write only)
2471         *   $C087+$s0  EPROM Bank Register          (write only)
2472         *   $C088+$s0  6551 Data                    (read/write)
2473         *   $C089+$s0  6551 Status / Program Reset  (read/write)
2474         *   $C08A+$s0  6551 Command                 (read/write)
2475         *   $C08B+$s0  6551 Control                 (read/write)
2476         *
2477         * Apple2IO passes SlotIO as an offset $0..$F within the mounted slot's
2478         * $C0s0-$C0sF range, hence BANKCLR/BANKREG are offsets $06/$07.
2479         *
2480         * IMPORTANT: this register is independent of C8 ownership.  A bank write
2481         * changes which EPROM bytes back logical $C800-$CFFF immediately, even
2482         * while the CPU is executing there.  It does not itself claim or release
2483         * the Apple II expansion-ROM window.
2484         */
2485        addr &= 0x0F;
2486        d8 &= 0xFF;
2487
2488        if(addr>=0x08 && addr<=0x0B)
2489            aciaSyncNow(ctx);
2490
2491        switch(addr)
2492        {
2493            case 0x04:      // $C084+$s0: 6818 Clock/RAM address register
2494                rtc.address = d8 & 0x3F;
2495                return 0x00;
2496
2497            case 0x05:      // $C085+$s0: selected 6818 Clock/RAM data
2498                rtcWriteData(d8);
2499                return 0x00;            
2500            case 0x06:      // $C086+$s0: EPROM Bank Register Clear
2501                // The hardware operation is selected by the address; the byte
2502                // written is irrelevant.  Return to the resident HostROM bank 0.
2503                state.romSelector = 0x00;
2504                if(bDebug) console.log("Serial Pro EPROM BANKCLR -> selector $00");
2505                return 0x00;
2506
2507            case 0x07:      // $C087+$s0: EPROM Bank Register
2508                // Retain the whole selector.  Only bits 0-1 are used to choose
2509                // the physical 2 KiB quarter; higher bits are decoded by firmware.
2510                state.romSelector = d8;
2511                if(bDebug)
2512                    console.log(
2513                        "Serial Pro EPROM BANKREG <- $"+d8.toString(16).padStart(2,"0")+
2514                        " (physical bank "+physicalHostROMBank()+")"
2515                    );
2516                return 0x00;
2517            case 0x08:      // $C088+$s0: 6551 Transmitter Data Register
2518                aciaWriteData(d8);
2519                return 0x00;
2520
2521            case 0x09:      // $C089+$s0: 6551 programmed reset; d8 is don't-care
2522                aciaProgramReset();
2523                return 0x00;
2524
2525            case 0x0A:      // $C08A+$s0: 6551 Command Register
2526                aciaWriteCommand(d8);
2527                return 0x00;
2528
2529            case 0x0B:      // $C08B+$s0: 6551 Control Register
2530                aciaWriteControl(d8);
2531                return 0x00;
2532        }
2533        return 0x00;
2534    };
2535
2536
2537    this.readHostROM = function(addr,ctx)
2538    {
2539        /*
2540         * $C800-$CFFF shared expansion-ROM view.
2541         *
2542         * The CPU-visible address selects one byte within a 2 KiB window, while
2543         * the Serial Pro EPROM selector chooses which physical 27C64 quarter is
2544         * behind that window:
2545         *
2546         *     EPROM = ((romSelector & $03) * $0800) + (CPU - $C800)
2547         *
2548         * This lookup is performed on every fetch.  Consequently a firmware
2549         * instruction such as STA $C087,Y can switch banks and the very next
2550         * opcode fetched at the same logical $C8xx address comes from the newly
2551         * selected physical bank, matching the observed AE firmware design.
2552         */
2553        var rel_addr;
2554
2555        if(ctx && Number.isFinite(ctx.rel_addr))
2556            rel_addr = ctx.rel_addr;
2557        else if(ctx && Number.isFinite(ctx.line))
2558            rel_addr = ctx.line + (addr & 0xFF);
2559        else
2560            rel_addr = (serialpro.action.HostROM.base || 0x800) + (addr & 0xFF);
2561
2562        if(!ownsExpansionROM()) return 0x00;
2563        var bank_addr = rel_addr - 0x800;
2564        var eprom_addr = (physicalHostROMBank() << 11) + bank_addr;
2565        var d8 = bank_addr>=0 && bank_addr<0x800
2566            ? SERIALPRO_EPROM[eprom_addr]
2567            : 0x00;
2568
2569        // $CFFF releases C8 ownership only.  It does NOT clear the AE bank
2570        // register; BANKCLR at $C086+$s0 is a separate hardware operation.
2571        if((rel_addr & 0x0FFF)==0x0FFF && !(ctx && ctx.bRO===true))
2572            releaseExpansionROM();
2573
2574        return d8 & 0xFF;
2575    };
2576
2577    /*
2578     * Peripheral controls: compact live RTC editor.
2579     *
2580     * The RTC display uses the same dashboard refresh sequencer and visual
2581     * sync convention as the Disk II surface map: fa-sync-alt when stopped,
2582     * fa-stop-circle while live updates are active.
2583     *
2584     * Input fields remain editable while live sync is active.  The refresh
2585     * callback never rewrites the field that currently owns keyboard focus,
2586     * so the user's cursor/selection remains undisturbed.  Invalid input
2587     * marks only the invalid field red and disables SET; the user's live-sync
2588     * state remains unchanged while editing.
2589     */
2590
2591    var rtcUI =
2592    {
2593         "syncRequested":false
2594        ,"controlID":""
2595        ,"refreshEvent":""
2596    };
2597
2598    var serialUI =
2599    {
2600         "controlID":""
2601        ,"refreshEvent":""
2602        ,"configDirty":true
2603    };
2604
2605
2606    function serialConfigWriteControl(value)
2607    {
2608        rtc.ram[0x23] = value & 0xFF;
2609        serialUI.configDirty = true;
2610        return rtc.ram[0x23];
2611    }
2612
2613    function serialConfigWriteCommand(value)
2614    {
2615        rtc.ram[0x24] = value & 0xFF;
2616        serialUI.configDirty = true;
2617        return rtc.ram[0x24];
2618    }
2619
2620    function rtcUIPad2(value) { return String(value).padStart(2,"0"); }
2621
2622    function rtcUIValues()
2623    {
2624        var date = rtcCurrentDate();
2625
2626        return {
2627             "date": date.getFullYear()
2628                    + "-" + rtcUIPad2(date.getMonth()+1)
2629                    + "-" + rtcUIPad2(date.getDate())
2630            ,"time": rtcUIPad2(date.getHours())
2631                    + ":" + rtcUIPad2(date.getMinutes())
2632                    + ":" + rtcUIPad2(date.getSeconds())
2633        };
2634    }
2635
2636    function rtcUIElement(controlID,suffix)
2637    {
2638        return document.getElementById(controlID+"_"+suffix);
2639    }
2640
2641    function rtcUIFieldValidity(element,valid)
2642    {
2643        if(element)
2644            element.style.borderColor = valid ? "transparent" : "#D00000";
2645    }
2646
2647    function rtcUIParse(controlID,markValidity)
2648    {
2649        var dateEl = rtcUIElement(controlID,"rtc_date");
2650        var timeEl = rtcUIElement(controlID,"rtc_time");
2651        if(!dateEl || !timeEl)
2652            return {"valid":false,"dateValid":false,"timeValid":false,"date":null};
2653
2654        var dm = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateEl.value);
2655        var tm = /^(\d{2}):(\d{2}):(\d{2})$/.exec(timeEl.value);
2656
2657        var dateValid = !!dm;
2658        var timeValid = !!tm;
2659        var year,month,day,hour,minute,second;
2660
2661        if(dateValid)
2662        {
2663            year  = Number(dm[1]);
2664            month = Number(dm[2]);
2665            day   = Number(dm[3]);
2666
2667            var calendar = new Date(0);
2668            calendar.setFullYear(year,month-1,day);
2669            calendar.setHours(12,0,0,0);
2670
2671            dateValid =
2672                month>=1 && month<=12 &&
2673                day>=1 && day<=31 &&
2674                calendar.getFullYear()==year &&
2675                calendar.getMonth()==month-1 &&
2676                calendar.getDate()==day;
2677        }
2678
2679        if(timeValid)
2680        {
2681            hour   = Number(tm[1]);
2682            minute = Number(tm[2]);
2683            second = Number(tm[3]);
2684
2685            timeValid =
2686                hour>=0 && hour<=23 &&
2687                minute>=0 && minute<=59 &&
2688                second>=0 && second<=59;
2689        }
2690
2691        if(markValidity)
2692        {
2693            rtcUIFieldValidity(dateEl,dateValid);
2694            rtcUIFieldValidity(timeEl,timeValid);
2695        }
2696
2697        if(!dateValid || !timeValid)
2698            return {
2699                 "valid":false
2700                ,"dateValid":dateValid
2701                ,"timeValid":timeValid
2702                ,"date":null
2703            };
2704
2705        var date = new Date(0);
2706        date.setFullYear(year,month-1,day);
2707        date.setHours(hour,minute,second,0);
2708
2709        // Also reject a local time which JavaScript normalised, for example a
2710        // non-existent wall-clock time inside a daylight-saving transition.
2711        var valid =
2712            date.getFullYear()==year &&
2713            date.getMonth()==month-1 &&
2714            date.getDate()==day &&
2715            date.getHours()==hour &&
2716            date.getMinutes()==minute &&
2717            date.getSeconds()==second;
2718
2719        if(markValidity && !valid)
2720        {
2721            rtcUIFieldValidity(dateEl,false);
2722            rtcUIFieldValidity(timeEl,false);
2723        }
2724
2725        return {
2726             "valid":valid
2727            ,"dateValid":valid && dateValid
2728            ,"timeValid":valid && timeValid
2729            ,"date":valid ? date : null
2730        };
2731    }
2732
2733    function rtcUIUpdateSyncButton()
2734    {
2735        if(!rtcUI.controlID) return false;
2736
2737        var monitor = rtcUIElement(rtcUI.controlID,"rtc_monitoring");
2738        if(!monitor) return false;
2739
2740        var active = rtcUI.syncRequested;
2741
2742        oCOM.POPUP.set_class(
2743             monitor
2744            ,"fa-stop-circle"
2745            ,"fa-sync-alt"
2746            ,active
2747        );
2748        monitor.title = active ? "Stop date/time sync" : "Start date/time sync";
2749        return active;
2750    }
2751
2752    function rtcUIUpdateSetButton(controlID,valid)
2753    {
2754        var setButton = rtcUIElement(controlID,"rtc_set");
2755        if(setButton) setButton.disabled = !valid;
2756        return !!valid;
2757     }
2758
2759    function rtcUIRefresh(controlID,preserveFocus)
2760    {
2761        var values = rtcUIValues();
2762        var dateEl = rtcUIElement(controlID,"rtc_date");
2763        var timeEl = rtcUIElement(controlID,"rtc_time");
2764        var activeEl = document.activeElement;
2765
2766        if(dateEl && (!preserveFocus || activeEl!==dateEl))
2767            dateEl.value = values.date;
2768
2769        if(timeEl && (!preserveFocus || activeEl!==timeEl))
2770            timeEl.value = values.time;
2771        return true;
2772    };
2773
2774
2775    this.deviceToolRTCInput = function(controlID)
2776    {
2777        rtcUI.controlID = controlID;
2778
2779        var parsed = rtcUIParse(controlID,true);
2780        rtcUIUpdateSetButton(controlID,parsed.valid);
2781
2782        return parsed.valid;
2783    };
2784
2785    this.deviceToolRTCSyncToggle = function(controlID)
2786    {
2787        rtcUI.controlID = controlID;
2788        rtcUI.syncRequested = !rtcUI.syncRequested;
2789
2790        var active = rtcUIUpdateSyncButton();
2791
2792         if(active)
2793        {
2794             rtcUIRefresh(controlID,true);
2795            var parsed = rtcUIParse(controlID,true);
2796            rtcUIUpdateSetButton(controlID,parsed.valid);
2797        }
2798
2799        return active;
2800    };
2801
2802    this.deviceToolRTCMonitoring = function()
2803    {
2804        if(!rtcUI.controlID || !rtcUI.syncRequested) return false;
2805
2806        // Keep the sync state visually stable while the user edits a field.
2807        // The focused field is never rewritten, so its cursor/selection stays
2808        // intact.  The other field may continue following the live RTC.
2809        rtcUIRefresh(rtcUI.controlID,true);
2810
2811        var parsed = rtcUIParse(rtcUI.controlID,true);
2812        rtcUIUpdateSetButton(rtcUI.controlID,parsed.valid);
2813        return true;
2814    };
2815
2816    function serialUIElement(controlID,suffix)
2817    {
2818        return document.getElementById(controlID+"_"+suffix);
2819    }
2820
2821    function serialUISetSelect(controlID,suffix,value,force)
2822    {
2823        var el = serialUIElement(controlID,suffix);
2824        if(el && (force || document.activeElement!==el))
2825            el.value = String(value);
2826    }
2827
2828    function serialUISetChecked(controlID,suffix,value,force)
2829    {
2830        var el = serialUIElement(controlID,suffix);
2831        if(el && (force || document.activeElement!==el))
2832            el.checked = !!value;
2833    }
2834
2835    function serialUISetSignal(controlID,suffix,value,known)
2836    {
2837        var el = serialUIElement(controlID,suffix);
2838        if(!el) return;
2839
2840        el.textContent = "●";
2841        el.style.color = !known ? "#999999" : (value ? "#008000" : "#A0A0A0");
2842    }
2843
2844    function serialUIPhysicalTitle()
2845    {
2846        if(!serialPhysical.connected)
2847            return "Connect a physical USB serial adapter through Web Serial";
2848
2849        var label = serialPhysical.openConfig ? serialPhysical.openConfig.label : "serial";
2850        var title = "Physical USB serial connected @ "+label;
2851
2852        if(serialPhysical.configStale)
2853            title += "; ACIA format changed — disconnect and reconnect to apply it";
2854
2855        if(serialPhysical.port && typeof(serialPhysical.port.getInfo)=="function")
2856        {
2857            var info = serialPhysical.port.getInfo() || {};
2858            if(Number.isFinite(info.usbVendorId))
2859                title += " VID $"+Number(info.usbVendorId).toString(16).toUpperCase().padStart(4,"0");
2860            if(Number.isFinite(info.usbProductId))
2861                title += " PID $"+Number(info.usbProductId).toString(16).toUpperCase().padStart(4,"0");
2862        }
2863
2864        return title;
2865    }
2866
2867    function serialUIRefresh(controlID)
2868    {
2869        if(!controlID) return false;
2870
2871        /*
2872         * The SER row represents the configuration saved by the Serial Pro ROM,
2873         * not necessarily the live 6551 registers.  This is what makes changes
2874         * made in the Bank-1 control panel appear in the browser immediately.
2875         *
2876         * If the ROM changed $23/$24 since the previous dashboard t
2876ick, force
2877         * these finite-choice controls even if one still owns browser focus.
2878         * Normal monitoring keeps the original focus-preservation behavior.
2879         */
2880        var configured = serialConfiguredState();
2881        var forceConfigured = !!serialUI.configDirty;
2882
2883        serialUISetSelect(controlID,"serial_baud",configured.baud,forceConfigured);
2884        serialUISetSelect(controlID,"serial_data",configured.dataBits,forceConfigured);
2885        serialUISetSelect(controlID,"serial_parity",configured.parity,forceConfigured);
2886        serialUISetSelect(controlID,"serial_stop",configured.stopBits,forceConfigured);
2887        serialUISetChecked(controlID,"serial_rxclock",configured.rxClockInternal,forceConfigured);
2888        serialUI.configDirty = false;
2889
2890        // CPU sync and modem outputs are live emulator/6551 state, not NVRAM.
2891        serialUISetChecked(controlID,"serial_cpu_sync",state.cpuSync);
2892        serialUISetChecked(controlID,"physical_dtr",aciaDTRAsserted());
2893        serialUISetChecked(controlID,"physical_rts",aciaRTSAsserted());
2894
2895        var button = serialUIElement(controlID,"physical_toggle");
2896        if(button)
2897        {
2898            button.disabled = typeof(navigator)=="undefined" || !navigator.serial;
2899            button.textContent = serialPhysical.connected ? "DISCONNECT" : "CONNECT";
2900            button.title = serialUIPhysicalTitle();
2901            button.style.color = serialPhysical.connected
2902                ? (serialPhysical.configStale ? "#C07000" : "#008000")
2903                : "";
2904        }
2905
2906        var signals = serialPhysical.signals;
2907        var known = serialPhysical.connected && !!signals;
2908        serialUISetSignal(controlID,"physical_cts",known && signals.cts,known);
2909        serialUISetSignal(controlID,"physical_dcd",known && signals.dcd,known);
2910        serialUISetSignal(controlID,"physical_dsr",known && signals.dsr,known);
2911        serialUISetSignal(controlID,"physical_ri", known && signals.ri, known);
2912
2913        var physicalLabel = serialUIElement(controlID,"physical_status");
2914        if(physicalLabel)
2915        {
2916            physicalLabel.textContent = serialPhysical.connected
2917                ? (serialPhysical.configStale ? "STALE" : "ONLINE")
2918                : "OFFLINE";
2919            physicalLabel.style.color = serialPhysical.connected
2920                ? (serialPhysical.configStale ? "#C07000" : "#008000")
2921                : "#777777";
2922            physicalLabel.title = serialUIPhysicalTitle();
2923        }
2924
2925        var baudSelect = serialUIElement(controlID,"serial_baud");
2926        if(baudSelect)
2927        {
2928            var programmed = aciaBaudRate();
2929            var effective = aciaEffectiveBaudRate();
2930
2931        }
2932
2933        var cpuSync = serialUIElement(controlID,"serial_cpu_sync");
2934        if(cpuSync)
2935            cpuSync.title = state.cpuSync
2936                ? "CPU sync ON: serial timing follows the CPU speed multiplier"
2937                : "CPU sync OFF: serial timing is normalized to approximately x1 regardless of CPU acceleration";
2938            var configuredLabel = serialFormatLabel(configured.control,configured.command);
2939            var activeLabel = serialFormatLabel(acia.control,acia.command);
2940            var pending = serialFormatSignature(configured.control,configured.command)
2941                != serialFormatSignature(acia.control,acia.command);
2942
2943            baudSelect.title =
2944                "Serial Pro configured "+configuredLabel
2945                +"; live 6551 "+activeLabel
2946                +(pending ? " (pending/reinitialization differs)" : "")
2947                +(state.cpuSync
2948                    ? "; effective live baud "+effective
2949                    : "; CPU sync is off so live serial timing remains approximately x1");
2950        return true;
2951    }
2952
2953    this.deviceToolSerialSetting = function(controlID,setting,value)
2954    {
2955        controlID = String(controlID || "");
2956        setting = String(setting || "");
2957
2958        // Apply elapsed cycles using the old serial format before changing it.
2959        aciaSyncNow();
2960
2961        /*
2962         * Browser-originated format changes update BOTH layers:
2963         *   - RTC RAM $23/$24, so the ROM control panel sees the same setting;
2964         *   - the live 6551, preserving the browser UI's immediate behavior.
2965         *
2966         * Each layer preserves its own unrelated bits.  This is important for
2967         * live DTR/RTS state, which must not be overwritten by a parity edit.
2968         */
2969        var configured = serialConfiguredState();
2970
2971        if(setting=="baud")
2972        {
2973            var baud = Number(value);
2974            var index = -1;
2975            for(var i=1;i<ACIA_BAUD_TABLE.length;i++)
2976                if(ACIA_BAUD_TABLE[i]===baud) { index=i; break; }
2977
2978            if(index<0) return false;
2979            serialConfigWriteControl((configured.control & 0xF0) | index);
2980            aciaWriteControl((acia.control & 0xF0) | index);
2981        }
2982        else if(setting=="data")
2983        {
2984            var bits = Number(value);
2985            if(bits<5 || bits>8) return false;
2986            var dataBits = (((8-bits) & 0x03) << 5);
2987            serialConfigWriteControl((configured.control & 0x9F) | dataBits);
2988            aciaWriteControl((acia.control & 0x9F) | dataBits);
2989        }
2990        else if(setting=="parity")
2991        {
2992            var parityBits = {
2993                 "none":0x00
2994                ,"odd":0x20
2995                ,"even":0x60
2996                ,"mark":0xA0
2997                ,"space":0xE0
2998            }[String(value)];
2999
3000            if(parityBits===undefined) return false;
3001            serialConfigWriteCommand((configured.command & 0x1F) | parityBits);
3002            aciaWriteCommand((acia.command & 0x1F) | parityBits);
3003        }
3004        else if(setting=="stop")
3005        {
3006            var stop = Number(value);
3007            if(stop!=1 && stop!=1.5 && stop!=2) return false;
3008
3009            // The 6551 has one SBN bit. With SBN=1 the effective result is
3010            // 1, 1.5 or 2 stop bits depending on word length/parity.
3011            serialConfigWriteControl(
3012                stop==1 ? (configured.control & 0x7F) : (configured.control | 0x80)
3013            );
3014            aciaWriteControl(
3015                stop==1 ? (acia.control & 0x7F) : (acia.control | 0x80)
3016            );
3017        }
3018        else return false;
3019
3020        serialUIRefresh(controlID);
3021        return true;
3022    };
3023
3024    this.deviceToolSerialFlag = function(controlID,setting,enabled)
3025    {
3026        enabled = !!enabled;
3027        // Preserve elapsed timing under the mode/register state that existed
3028        // before this UI-originated change.
3029        aciaSyncNow();
3030
3031        if(setting=="cpusync")
3032        {
3033            state.cpuSync = enabled;
3034            serialPhysicalCheckConfiguration();
3035        }
3036        else if(setting=="rxclock")
3037        {
3038            var configured = serialConfiguredState();
3039            serialConfigWriteControl(enabled
3040                ? (configured.control | 0x10)
3041                : (configured.control & 0xEF));
3042            aciaWriteControl(enabled
3043                ? (acia.control | 0x10)
3044                : (acia.control & 0xEF));
3045        }
3046        else if(setting=="dtr")
3047        {
3048            aciaWriteCommand(enabled
3049                ? (acia.command | 0x01)
3050                : (acia.command & 0xFE));
3051        }
3052        else if(setting=="rts")
3053        {
3054            /*
3055             * Manual RTS control chooses the non-IRQ transmitter mode TIC=10
3056             * when asserted and TIC=00 when released. Clear echo mode because
3057             * echo forces RTS asserted regardless of TIC.
3058             */
3059            var command = acia.command & ~0x1C;
3060            if(enabled) command |= 0x08;
3061            aciaWriteCommand(command);
3062        }
3063        else return false;
3064
3065        serialUIRefresh(controlID);
3066        return true;
3067    };
3068
3069    this.deviceToolPhysicalToggle = async function(controlID)
3070    {
3071        try
3072        {
3073            if(serialPhysical.connected)
3074                await serialPhysicalDisconnect(false);
3075            else
3076                await serialPhysicalConnect();
3077        }
3078        finally
3079        {
3080            serialUIRefresh(controlID);
3081        }
3082
3083        return serialPhysical.connected;
3084    };
3085
3086    this.deviceToolSerialMonitoring = function()
3087    {
3088        if(!serialUI.controlID) return false;
3089
3090        // ROM writes to RTC RAM $23/$24 set serialUI.configDirty.  The normal
3091        // dashboard cadence then propagates the saved format into the SER row.
3092        // CPU target speed may also change without touching an ACIA register.
3093        if(serialPhysical.connected) serialPhysicalCheckConfiguration();
3094        return serialUIRefresh(serialUI.controlID);
3095    };
3096
3097    /*
3098     * Serial-terminal endpoint.
3099     *
3100     * This UI exists before the 6551 implementation so Stage 2 has a stable
3101     * browser endpoint to attach to:
3102     *
3103     *   serialTerminalReadByte()   terminal -> future 6551 RX
3104     *   serialTerminalWriteByte()  future 6551 TX -> terminal
3105     *
3106     * Enter queues the exact 8-bit input line followed by CR ($0D).
3107     */
3108    var serialTerminalState =
3109    {
3110         "toCard":[]
3111        ,"transcript":[]
3112        ,"transcriptChars":0
3113        ,"lastRxCR":false
3114        ,"theme":"dark"
3115        ,"displayMode":"ascii"
3116    };
3117
3118    /*
3119     * Stage 3A: optional physical Web Serial transport.
3120     *
3121     * This is deliberately a transport beside oTERM, not part of the 
31216551
3122     * register model. The ACIA remains synchronous from the Apple II's point
3123     * of view while the browser/OS/USB-UART serializes bytes in real time.
3124     *
3125     * The physical port is opened using the ACIA format that exists at connect
3126     * time. Web Serial can represent 7/8 data bits, 1/2 stop bits and
3127     * none/even/odd parity. Unsupported 6551 formats are rejected rather than
3128     * silently approximated. ACIA format changes while connected mark the
3129     * physical link stale; disconnect/reconnect applies the new format.
3130     *
3131     * DTR, RTS and BREAK are synchronized live with setSignals(). CTS/DCD/DSR
3132     * are sampled for diagnostics, but Stage 3A intentionally does not feed
3133     * them into the emulated ACIA. Many cheap USB-UART modules expose only
3134     * TXD/RXD and would otherwise appear permanently blocked by unused modem
3135     * inputs. A later stage can make modem-line bridging explicit/optional.
3136     */
3137    var serialPhysical =
3138    {
3139         "port":null
3140        ,"reader":null
3141        ,"writer":null
3142        ,"readTask":null
3143        ,"txTask":null
3144        ,"txBusy":false
3145        ,"txQueue":[]
3146        ,"connected":false
3147        ,"closing":false
3148        ,"openConfig":null
3149        ,"configStale":false
3150        ,"signals":null
3151        ,"signalTimer":null
3152        ,"lastIncomingCR":false
3153        ,"lastError":""
3154        ,"lastSignalError":""
3155    };
3156
3157    function serialPhysicalErrorText(error)
3158    {
3159        if(!error) return "Unknown error";
3160        return error.name && error.message
3161            ? error.name+": "+error.message
3162            : (error.message || String(error));
3163    }
3164
3165    this.serialTerminalConsoleWrite = function(text,meta)
3166    {
3167        text = String(text===undefined ? "" : text);
3168        if(!text.length) return 0;
3169        if(text.charAt(text.length-1)!="\n") text += "\n";
3170
3171        serialTerminalRemember("meta",text);
3172        var terminal = serialTerminalLive();
3173        if(terminal) terminal.write(text,"meta");
3174        return text.length;
3175    };
3176
3177    function serialPhysicalMeta(text)
3178    {
3179        return serialpro.serialTerminalConsoleWrite(
3180            text,
3181            {"source":"webserial"}
3182        );
3183    }
3184
3185    function serialPhysicalConfiguration()
3186    {
3187        var programmedBaud = aciaBaudRate();
3188        var baud = aciaEffectiveBaudRate();
3189        var dataBits = aciaWordLength();
3190        var stopBits = aciaStopBits();
3191        var parity = aciaParity();
3192        var receiverInternal = (acia.control & 0x10)!=0;
3193
3194        if(!Number.isFinite(baud) || baud<=0 || Math.floor(baud)!=baud)
3195            return {
3196                 "ok":false
3197                ,"reason":"Web Serial requires a positive integer effective baud rate; ACIA baud is "
3198                    +String(programmedBaud)+", effective baud is "+String(baud)
3199            };
3200
3201        if(!receiverInternal)
3202            return {"ok":false,
3203                    "reason":"Web Serial cannot reproduce the ACIA external receive clock; select the internal receive rate"};
3204
3205        if(dataBits!=7 && dataBits!=8)
3206            return {"ok":false,
3207                    "reason":"Web Serial supports 7 or 8 data bits; ACIA is configured for "+dataBits};
3208
3209        if(stopBits!=1 && stopBits!=2)
3210            return {"ok":false,
3211                    "reason":"Web Serial supports 1 or 2 stop bits; ACIA is configured for "+stopBits};
3212
3213        if(parity!="none" && parity!="even" && parity!="odd")
3214            return {"ok":false,
3215                    "reason":"Web Serial supports none/even/odd parity; ACIA parity is "+parity};
3216
3217        var parityLetter = parity=="none" ? "N" : (parity=="even" ? "E" : "O");
3218        var multiplier = aciaCPUMultiplier();
3219        var signature = [baud,dataBits,parity,stopBits].join("/");
3220        var label = baud+" "+dataBits+parityLetter+stopBits;
3221        if(state.cpuSync && Math.abs(multiplier-1)>1e-9)
3222            label += " (ACIA "+programmedBaud+" x"+multiplier+")";
3223
3224        return {
3225             "ok":true
3226            ,"signature":signature
3227            ,"label":label
3228            ,"options":{
3229                 "baudRate":baud
3230                ,"dataBits":dataBits
3231                ,"stopBits":stopBits
3232                ,"parity":parity
3233                ,"flowControl":"none"
3234            }
3235        };
3236    }
3237
3238    function serialPhysicalUpdateButton()
3239    {
3240        var popup = document.getElementById("serialProTerminal_popup");
3241        if(!popup) return false;
3242
3243        var icon = popup.querySelector("[data-serial-webserial]");
3244        if(!icon) return false;
3245
3246        var title;
3247        if(serialPhysical.connected && serialPhysical.configStale)
3248        {
3249            title = "USB serial connected, but ACIA format changed; reconnect to apply it";
3250            icon.style.color = "#C07000";
3251        }
3252        else if(serialPhysical.connected)
3253        {
3254            title = "Disconnect physical USB serial port";
3255            icon.style.color = "#008000";
3256        }
3257        else
3258        {
3259            title = "Connect physical USB serial port (Web Serial)";
3260            icon.style.color = "";
3261        }
3262
3263        icon.title = title;
3264        if(icon.parentElement) icon.parentElement.title = title;
3265        return serialPhysical.connected;
3266    }
3267
3268    function serialPhysicalCheckConfiguration()
3269    {
3270        if(!serialPhysical || !serialPhysical.connected)
3271            return false;
3272
3273        var config = serialPhysicalConfiguration();
3274        var stale = !config.ok || !serialPhysical.openConfig ||
3275                    config.signature!=serialPhysical.openConfig.signature;
3276        var changed = stale!=serialPhysical.configStale;
3277
3278        serialPhysical.configStale = stale;
3279        serialPhysicalUpdateButton();
3280
3281        if(changed && stale)
3282            serialPhysicalMeta(
3283                "Web Serial ACIA format changed; reconnect the USB serial port before physical byte transfer continues."
3284            );
3285
3286        return stale;
3287    }
3288
3289    async function serialPhysicalSyncOutputs()
3290    {
3291        if(!serialPhysical || !serialPhysical.connected || !serialPhysical.port)
3292            return false;
3293
3294        try
3295        {
3296            await serialPhysical.port.setSignals({
3297                 "dataTerminalReady":aciaDTRAsserted()
3298                ,"requestToSend":aciaRTSAsserted()
3299                ,"break":aciaBreakActive()
3300            });
3301            serialPhysical.lastSignalError = "";
3302            return true;
3303        }
3304        catch(error)
3305        {
3306            var message = serialPhysicalErrorText(error);
3307            if(message!=serialPhysical.lastSignalError)
3308            {
3309                serialPhysical.lastSignalError = message;
3310                serialPhysicalMeta("Web Serial output-signal warning: "+message);
3311            }
3312            return false;
3313        }
3314    }
3315
3316    async function serialPhysicalPollSignals()
3317    {
3318        if(!serialPhysical.connected || !serialPhysical.port)
3319            return null;
3320
3321        try
3322        {
3323            var signals = await serialPhysical.port.getSignals();
3324            serialPhysical.signals = {
3325                 "cts":!!signals.clearToSend
3326                ,"dcd":!!signals.dataCarrierDetect
3327                ,"dsr":!!signals.dataSetReady
3328                ,"ri":!!signals.ringIndicator
3329            };
3330            serialPhysical.lastSignalError = "";
3331            return serialPhysical.signals;
3332        }
3333        catch(error)
3334        {
3335            var message = serialPhysicalErrorText(error);
3336            if(message!=serialPhysical.lastSignalError)
3337            {
3338                serialPhysical.lastSignalError = message;
3339                serialPhysicalMeta("Web Serial input-signal warning: "+message);
3340            }
3341            return null;
3342        }
3343    }
3344
3345    function serialPhysicalDisplayByte(d8)
3346    {
3347        d8 &= 0xFF;
3348        if(d8==0x0D)
3349        {
3350            serialPhysical.lastIncomingCR = true;
3351            return "\n";
3352        }
3353        if(d8==0x0A)
3354        {
3355            if(serialPhysical.lastIncomingCR)
3356            {
3357                serialPhysical.lastIncomingCR = false;
3358                return "";
3359            }
3360            return "\n";
3361        }
3362
3363        serialPhysical.lastIncomingCR = false;
3364        if(d8==0x09) return "\t";
3365        if(d8>=0x20 && d8<=0x7E) return String.fromCharCode(d8);
3366        return "[$"+serialTerminalHexByte(d8)+"]";
3367    }
3368
3369    function serialPhysicalReceiveChunk(chunk)
3370    {
3371        if(!chunk || !chunk.length || serialPhysical.configStale)
3372            return 0;
3373
3374        var display = "";
3375        for(var i=0;i<chunk.length;i++)
3376            display += serialPhysicalDisplayByte(chunk[i] & 0xFF);
3377
3378        var accepted = serialLineInjectBytes(
3379            chunk,
3380            {"source":"webserial"}
3381        );        
3382
3383        if(display.length)
3384        {
3385            serialTerminalRemember("tx",display);
3386            var terminal = serialTerminalLive();
3387            if(terminal) terminal.write(display,"tx");
3388        }
3389        return accepted;
3390    }
3391
3392    async function serialPhysicalReadLoop(port)
3393    {
3394        while(serialPhysical.connected && serialPhysical.port===port && port.readable)
3395        {
3396            var reader = port.readable.getReader();
3397            serialPhysical.reader = reader;
3398            try
3399            {
3400                while(serialPhysical.connected)
3401                {
3402                    var result = await reader.read();
3403                    if(result.done) break;
3404                    if(result.value) serialPhysicalReceiveChunk(result.value);
3405                }
3406            }
3407            catch(error)
3408            {
3409                if(serialPhysical.connected && !serialPhysical.closing)
3410                    serialPhysicalMeta("Web Serial read event: "+serialPhysicalErrorText(error));
3411            }
3412            finally
3413            {
3414                try { reader.releaseLock(); } catch(ignore) {}
3415                if(serialPhysical.reader===reader) serialPhysical.reader = null;
3416            }
3417
3418            if(!serialPhysical.connected || !port.readable) break;
3419        }
3420
3421        if(serialPhysical.connected && !serialPhysical.closing)
3422            setTimeout(function(){ serialPhysicalDisconnect(true); },0);
3423    }
3424
3425    function serialPhysicalFlushTX()
3426    {
3427        if(serialPhysical.txBusy || !serialPhysical.connected ||
3428           !serialPhysical.writer || serialPhysical.configStale)
3429            return serialPhysical.txTask;
3430
3431        serialPhysical.txBusy = true;
3432        serialPhysical.txTask = (async function()
3433        {
3434            try
3435            {
3436                while(serialPhysical.connected && serialPhysical.writer &&
3437                      !serialPhysical.configStale && serialPhysical.txQueue.length)
3438                {
3439                    var count = Math.min(256,serialPhysical.txQueue.length);
3440                    var bytes = new Uint8Array(serialPhysical.txQueue.splice(0,count));
3441                    await serialPhysical.writer.write(bytes);
3442                }
3443            }
3444            catch(error)
3445            {
3446                serialPhysical.lastError = serialPhysicalErrorText(error);
3447                serialPhysicalMeta("Web Serial write failed: "+serialPhysical.lastError);
3448                setTimeout(function(){ serialPhysicalDisconnect(true); },0);
3449            }
3450            finally { serialPhysical.txBusy = false; }
3451        })();
3452
3453        return serialPhysical.txTask;
3454    }
3455
3456    function serialPhysicalWriteByte(d8)
3457    {
3458        if(!serialPhysical || !serialPhysical.connected ||
3459           !serialPhysical.writer || serialPhysical.configStale)
3460            return false;
3461
3462        serialPhysical.txQueue.push(d8 & 0xFF);
3463        serialPhysicalFlushTX();
3464        return true;
3465    }
3466
3467    function serialPhysicalPortLabel(port)
3468    {
3469        if(!port || typeof(port.getInfo)!="function") return "USB serial port";
3470
3471        var info = port.getInfo() || {};
3472        var parts = [];
3473        if(Number.isFinite(info.usbVendorId))
3474            parts.push("VID $"+Number(info.usbVendorId).toString(16).toUpperCase().padStart(4,"0"));
3475        if(Number.isFinite(info.usbProductId))
3476            parts.push("PID $"+Number(info.usbProductId).toString(16).toUpperCase().padStart(4,"0"));
3477        return parts.length ? parts.join(" ") : "USB serial port";
3478    }
3479
3480    async function serialPhysicalConnect()
3481    {
3482        if(serialPhysical.connected) return true;
3483
3484        if(typeof(navigator)=="undefined" || !navigator.serial)
3485        {
3486            serialPhysicalMeta(
3487                "Web Serial is unavailable. Use a supported Chromium browser in a secure context (HTTPS)."
3488            );
3489            return false;
3490        }
3491
3492        var config = serialPhysicalConfiguration();
3493        if(!config.ok)
3494        {
3495            serialPhysicalMeta("Web Serial cannot open this ACIA format: "+config.reason+".");
3496            return false;
3497        }
3498
3499        var port = null;
3500        try
3501        {
3502            // Deliberately unfiltered: let the browser chooser select the CH340
3503            // or another compatible USB serial adapter explicitly.
3504            port = await navigator.serial.requestPort();
3505            await port.open(config.options);
3506
3507            if(!port.writable)
3508                throw new Error("Selected serial port has no writable stream");
3509
3510            serialPhysical.port = port;
3511            serialPhysical.writer = port.writable.getWriter();
3512            serialPhysical.connected = true;
3513            serialPhysical.closing = false;
3514            serialPhysical.openConfig = config;
3515            serialPhysical.configStale = false;
3516            serialPhysical.lastError = "";
3517            serialPhysical.txQueue = [];
3518
3519            serialPhysicalUpdateButton();
3520            await serialPhysicalSyncOutputs();
3521            await serialPhysicalPollSignals();
3522
3523            serialPhysical.readTask = serialPhysicalReadLoop(port);
3524            serialPhysical.signalTimer = setInterval(serialPhysicalPollSignals,500);
3525
3526            serialPhysicalMeta(
3527                "Web Serial connected: "+serialPhysicalPortLabel(port)+" @ "+config.label+"."
3528            );
3529            return true;
3530        }
3531        catch(error)
3532        {
3533            serialPhysical.lastError = serialPhysicalErrorText(error);
3534            if(port)
3535            {
3536                try { await port.close(); } catch(ignore) {}
3537            }
3538
3539            serialPhysical.port = null;
3540            serialPhysical.writer = null;
3541            serialPhysical.connected = false;
3542            serialPhysical.openConfig = null;
3543            serialPhysicalUpdateButton();
3544
3545            if(error && error.name=="NotFoundError")
3546                serialPhysicalMeta("Web Serial device selection cancelled.");
3547            else
3548                serialPhysicalMeta("Web Serial connection failed: "+serialPhysical.lastError);
3549            return false;
3550        }
3551    }
3552
3553    async function serialPhysicalDisconnect(silent)
3554    {
3555        if(serialPhysical.closing) return false;
3556        if(!serialPhysical.port && !serialPhysical.connected) return false;
3557
3558        serialPhysical.closing = true;
3559        serialPhysical.connected = false;
3560        serialPhysicalUpdateButton();
3561
3562        if(serialPhysical.signalTimer)
3563        {
3564            clearInterval(serialPhysical.signalTimer);
3565            serialPhysical.signalTimer = null;
3566        }
3567
3568        var reader = serialPhysical.reader;
3569        if(reader)
3570        {
3571            try { await reader.cancel(); } catch(ignore) {}
3572        }
3573
3574        if(serialPhysical.readTask)
3575        {
3576            try { await serialPhysical.readTask; } catch(ignore) {}
3577        }
3578        if(serialPhysical.txTask)
3579        {
3580            try { await serialPhysical.txTask; } catch(ignore) {}
3581        }
3582        if(serialPhysical.writer)
3583        {
3584            try { serialPhysical.writer.releaseLock(); } catch(ignore) {}
3585        }
3586
3587        var port = serialPhysical.port;
3588        if(port)
3589        {
3590            try { await port.close(); } catch(ignore) {}
3591        }
3592
3593        serialPhysical.port = null;
3594        serialPhysical.reader = null;
3595        serialPhysical.writer = null;
3596        serialPhysical.readTask = null;
3597        serialPhysical.txTask = null;
3598        serialPhysical.txBusy = false;
3599        serialPhysical.txQueue = [];
3600        serialPhysical.openConfig = null;
3601        serialPhysical.configStale = false;
3602        serialPhysical.signals = null;
3603        serialPhysical.closing = false;
3604        serialPhysicalUpdateButton();
3605
3606        if(!silent) serialPhysicalMeta("Web Serial disconnected.");
3607        return true;
3608    }
3609
3610    function serialTerminalHexByte(d8)
3611    {
3612        return ("0"+((d8 & 0xFF).toString(16).toUpperCase())).slice(-2);
3613    }
3614
3615    this.serialPhysicalState = function()
3616    {
3617        var info = serialPhysical.port && typeof(serialPhysical.port.getInfo)=="function"
3618            ? serialPhysical.port.getInfo()
3619            : {};
3620
3621        return {
3622             "supported":typeof(navigator)!="undefined" && !!navigator.serial
3623            ,"connected":serialPhysical.connected
3624            ,"configuration":serialPhysical.openConfig
3625                ? serialPhysical.openConfig.options
3626                : null
3627            ,"configurationLabel":serialPhysical.openConfig
3628                ? serialPhysical.openConfig.label
3629                : null
3630            ,"configurationStale":serialPhysical.configStale
3631            ,"usbVendorId":Number.isFinite(info.usbVendorId) ? info.usbVendorId : null
3632            ,"usbProductId":Number.isFinite(info.usbProductId) ? info.usbProductId : null
3633            ,"signals":serialPhysical.signals
3634            ,"txQueued":serialPhysical.txQueue.length
3635            ,"lastError":serialPhysical.lastError
3636        };
3637    };
3638
3639    this.serialPhysicalConnect = function()
3640    {
3641        return serialPhysicalConnect();
3642    };
3643
3644    this.serialPhysicalDisconnect = function()
3645    {
3646        return serialPhysicalDisconnect(false);
3647    };
3648
3649    this.serialPhysicalToggle = function()
3650    {
3651        return serialPhysical.connected
3652            ? serialPhysicalDisconnect(false)
3653            : serialPhysicalConnect();
3654    };
3655
3656    this.serialPhysicalPollSignals = function()
3657    {
3658        return serialPhysicalPollSignals();
3659    };
3660
3661    function serialTerminalPopup()
3662    {
3663        var popup = document.getElementById("serialProTerminal_popup");
3664        if(popup) return popup;
3665
3666        popup = document.createElement("div");
3667        popup.id = "serialProTerminal_popup";
3668        popup.hidden = true;
3669        popup.className = "appbox com_popup_frame serialpro_terminal_popup";
3670        popup.style.cssText =
3671            "position:absolute;z-index:3;left:800px;top:32px;"
3672            +"width:450px;height:450px;text-align:left;"
3673            +"padding:0px;margin:0px";
3674        document.body.appendChild(popup);
3675        return popup;
3676    }
3677
3678    function serialTerminalSlotN()
3679    {
3680        return serialpro.mount ? Number(serialpro.mount.slotN) : null;
3681    }
3682
3683    function serialTerminalLive()
3684    {
3685        var popup = document.getElementById("serialProTerminal_popup");
3686        var slotN = serialTerminalSlotN();
3687
3688        if(!popup || popup.hidden!==false || slotN===null) return null;
3689        if(Number(popup.getAttribute("data-slotN"))!==slotN) return null;
3690        return popup._terminal || null;
3691    }
3692
3693    function serialTerminalRemember(kind,text)
3694    {
3695        text = String(text===undefined ? "" : text);
3696        if(!text.length) return;
3697
3698        var rows = serialTerminalState.transcript;
3699        var last = rows.length ? rows[rows.length-1] : null;
3700
3701        if(last && last.kind===kind && !last.bytes && last.text.length<4096)
3702            last.text += text;
3703        else
3704            rows.push({"kind":kind,"text":text});
3705
3706        serialTerminalState.transcriptChars += text.length;
3707        while(serialTerminalState.transcriptChars>65536 && rows.length>1)
3708        {
3709            var dropped = rows.shift();
3710            serialTerminalState.transcriptChars -= dropped.bytes
3711                ? dropped.bytes.length
3712                : dropped.text.length;
3713        }
3714    }
3715
3716    function serialTerminalRememberByte(d8)
3717    {
3718        d8 &= 0xFF;
3719
3720        var rows = serialTerminalState.transcript;
3721        var last = rows.length ? rows[rows.length-1] : null;
3722
3723        if(last && last.kind==="rx" && last.bytes && last.bytes.length<4096)
3724            last.bytes.push(d8);
3725        else
3726            rows.push({"kind":"rx","bytes":[d8]});
3727
3728        serialTerminalState.transcriptChars++;
3729        while(serialTerminalState.transcriptChars>65536 && rows.length>1)
3730        {
3731            var dropped = rows.shift();
3732            serialTerminalState.transcriptChars -= dropped.bytes
3733                ? dropped.bytes.length
3734                : dropped.text.length;
3735        }
3736    }
3737
3738    function serialTerminalDisplayByte(d8)
3739    {
3740        d8 &= 0xFF;
3741
3742        /*
3743         * Apple II text normally travels with bit 7 set. APPLE ASCII mode is
3744         * intended for LIST/PR# capture and printer-like output: strip bit 7
3745         * before interpreting CR/LF/TAB and printable ASCII. RAW mode retains
3746         * the original diagnostic rendering of non-printing 8-bit values.
3747         */
3748        var ascii = serialTerminalState.displayMode==="ascii";
3749        var c = ascii ? (d8 & 0x7F) : d8;
3750
3751        if(c===0x0D)
3752        {
3753            serialTerminalState.lastRxCR = true;
3754            return "\n";
3755        }
3756
3757        if(c===0x0A)
3758        {
3759            if(serialTerminalState.lastRxCR)
3760            {
3761                serialTerminalState.lastRxCR = false;
3762                return "";
3763            }
3764            return "\n";
3765        }
3766
3767        serialTerminalState.lastRxCR = false;
3768        if(c===0x09) return "\t";
3769
3770        if(ascii)
3771        {
3772            // Backspace/NUL and other Apple control traffic should not turn a
3773            // human-readable printer/listing stream back into byte notation.
3774            if(c===0x0C) return "\n";              // form feed: readable break
3775            if(c>=0x20 && c<=0x7E) return String.fromCharCode(c);
3776            return "";
3777        }
3778
3779        if(c>=0x20 && c<=0x7E) return String.fromCharCode(c);
3780
3781        // Keep otherwise non-printing octets visible in the terminal.
3782        return "[$"+serialTerminalHexByte(d8)+"]";
3783    }
3784
3785    function serialTerminalRenderRow(row)
3786    {
3787        if(row && row.bytes)
3788        {
3789            var text = "";
3790            for(var i=0;i<row.bytes.length;i++)
3791                text += serialTerminalDisplayByte(row.bytes[i]);
3792            return text;
3793        }
3794
3795        return row && row.text ? row.text : "";
3796    }
3797
3798    function serialTerminalRenderTranscript(terminal)
3799    {
3800        if(!terminal || !terminal._o || !terminal._o.DOM) return false;
3801
3802        terminal._o.DOM.output.innerHTML = "";
3803        serialTerminalState.lastRxCR = false;
3804
3805        var rows = serialTerminalState.transcript;
3806        for(var i=0;i<rows.length;i++)
3807        {
3808            var text = serialTerminalRenderRow(rows[i]);
3809            if(text.length) terminal.write(text,rows[i].kind);
3810        }
3811
3812        return true;
3813    }
3814
3815    this.serialTerminalQueueLine = function(line)
3816    {
3817        line = String(line===undefined ? "" : line);
3818        var bytes = new Uint8Array(line.length + 1);
3819
3820        for(var i=0;i<line.length;i++)
3821             bytes[i] = line.charCodeAt(i) & 0xFF;
3822
3823        // ENTER on the terminal is the remote endpoint sending CR.
3824        bytes[line.length] = 0x0D;
3825        serialTerminalRemember("tx",">"+line+"\n");
3826        return serialLineInjectBytes(
3827            bytes,
3828            {"source":"oterm"}
3829        );
3830
3831    };
3832
3833    this.serialTerminalPending = function()
3834    {
3835        return serialTerminalState.toCard.length + (acia.rxFull ? 1 : 0) + (acia.rxBusy ? 1 : 0);
3836    };
3837
3838    /*
3839     * Diagnostic/configuration API for the simulated modem side.
3840     * Boolean line names use their logical meaning, hiding the 6551's active-low
3841     * pin names: cts=true means CTSB low/clear-to-send, dcd=true means carrier
3842     * detected, and dsr=true means data set ready.
3843     */
3844    this.serialTerminalACIAState = function()
3845    {
3846        aciaSyncNow();
3847        return aciaDescribe();
3848    };
3849
3850    this.serialTerminalSetModemLines = function(lines)
3851    {
3852        aciaSyncNow();
3853        lines = lines || {};
3854
3855        var oldCTS = acia.ctsClear;
3856        var modemChanged = false;
3857
3858        if(Object.prototype.hasOwnProperty.call(lines,"cts"))
3859            acia.ctsClear = !!lines.cts;
3860
3861        if(Object.prototype.hasOwnProperty.call(lines,"dcd"))
3862        {
3863            var nextDCD = !!lines.dcd;
3864            modemChanged = modemChanged || nextDCD!=acia.dcdDetected;
3865            acia.dcdDetected = nextDCD;
3866        }
3867
3868        if(Object.prototype.hasOwnProperty.call(lines,"dsr"))
3869        {
3870            var nextDSR = !!lines.dsr;
3871            modemChanged = modemChanged || nextDSR!=acia.dsrReady;
3872            acia.dsrReady = nextDSR;
3873        }
3874
3875        if(modemChanged)
3876            aciaLatchModemStatusChange();
3877
3878        // CTSB returning low allows a held transmitter frame to begin.
3879        if(!oldCTS && acia.ctsClear)
3880            aciaTryTransmit();
3881
3882        return aciaDescribe();
3883    };
3884
3885    this.serialTerminalReadByte = function()
3886    {
3887        aciaSyncNow();
3888        // Diagnostic helper: consume through the same receive-register path
3889        // used by CPU reads so console tests cannot bypass Stage 2C semantics.
3890        if(!acia.rxFull)
3891            aciaPrimeReceiver();
3892
3893        return acia.rxFull ? aciaReadData(null) : null;
3894    };
3895
3896    this.serialTerminalWriteByte = function(d8)
3897    {
3898        d8 &= 0xFF;
3899        serialTerminalRememberByte(d8);
3900
3901        var text = serialTerminalDisplayByte(d8);
3902        if(!text.length) return d8 & 0xFF;
3903
3904        var terminal = serialTerminalLive();
3905        if(terminal) terminal.write(text,"rx");
3906
3907        return d8 & 0xFF;
3908    };
3909
3910    this.serialTerminalWriteText = function(text)
3911    {
3912        text = String(text===undefined ? "" : text);
3913        for(var i=0;i<text.length;i++)
3914            serialpro.serialTerminalWriteByte(text.charCodeAt(i) & 0xFF);
3915        return text.length;
3916    };
3917
3918    this.serialTerminalClear = function()
3919    {
3920        serialTerminalState.transcript = [];
3921        serialTerminalState.transcriptChars = 0;
3922        serialTerminalState.lastRxCR = false;
3923
3924        var terminal = serialTerminalLive();
3925        if(terminal) terminal.clear();
3926        return true;
3927    };
3928
3929    function serialTerminalSelectionInsideOutput(terminal)
3930    {
3931        if(!terminal || !terminal._o || !terminal._o.DOM ||
3932           typeof(window)==="undefined" || typeof(window.getSelection)!="function")
3933            return false;
3934
3935        var selection = window.getSelection();
3936        if(!selection || selection.rangeCount<1 || selection.isCollapsed)
3937            return false;
3938
3939        var output = terminal._o.DOM.output;
3940        function inside(node)
3941        {
3942            if(!node) return false;
3943            if(node.nodeType===3) node = node.parentNode;
3944            return node===output || output.contains(node);
3945        }
3946
3947        return inside(selection.anchorNode) || inside(selection.focusNode);
3948    }
3949
3950    function serialTerminalProtectStreaming(terminal)
3951    {
3952        if(!terminal || terminal._serialProWriteWrapped) return false;
3953
3954        var dom = terminal._o && terminal._o.DOM;
3955        if(!dom) return false;
3956
3957        // Some emulator-level UI styles disable text selection. Override that
3958        // locally: the Serial Pro output is intentionally printer-like text.
3959        if(dom.output && dom.output.style)
3960        {
3961            dom.output.style.userSelect = "text";
3962            dom.output.style.webkitUserSelect = "text";
3963        }
3964
3965        var baseWrite = terminal.write.bind(terminal);
3966        terminal.write = function(text,channel)
3967        {
3968            var input = dom.input;
3969            var preserveSelection = serialTerminalSelectionInsideOutput(terminal);
3970            var scrollTop = dom.container.scrollTop;
3971            var scrollLeft = dom.container.scrollLeft;
3972
3973            // oTERM write() currently calls resetCommand(). Preserve an input
3974            // line being typed while asynchronous serial bytes continue to
3975            // arrive; this also prevents a stream from stealing edit state.
3976            var inputValue = input ? input.value : "";
3977            var inputActive = input && document.activeElement===input;
3978            var selectionStart = input ? input.selectionStart : 0;
3979            var selectionEnd = input ? input.selectionEnd : 0;
3980
3981            var result = baseWrite(text,channel);
3982
3983            if(input)
3984            {
3985                input.value = inputValue;
3986                // Never refocus the live command field across a streamed write
3987                // while browser output text is selected; focus would collapse
3988                // the Range the user is trying to keep/copy.
3989                if(inputActive && !preserveSelection)
3990                {
3991                    try { input.focus({"preventScroll":true}); }
3992                    catch(ignore) { input.focus(); }
3993                    try { input.setSelectionRange(selectionStart,selectionEnd); }
3994                    catch(ignore) {}
3995                }
3996            }
3997
3998            /*
3999             * Appending new Text nodes does not invalidate the browser Range.
4000             * Suppress oTERM's automatic scroll-to-input while a non-collapsed
4001             * output selection exists so the selected visual scope stays put
4002             * even though the stream continues growing below it.
4003             */
4004            if(preserveSelection)
4005            {
4006                dom.container.scrollTop = scrollTop;
4007                dom.container.scrollLeft = scrollLeft;
4008            }
4009
4010            return result;
4011        };
4012
4013        terminal._serialProWriteWrapped = true;
4014        return true;
4015    }
4016
4017    function serialTerminalApplyDisplayMode()
4018    {
4019        var popup = document.getElementById("serialProTerminal_popup");
4020        if(!popup) return false;
4021
4022        var button = popup.querySelector("[data-serial-terminal-display]");
4023        if(!button) return false;
4024
4025        var ascii = serialTerminalState.displayMode==="ascii";
4026        button.textContent = ascii ? "ASCII" : "RAW";
4027        button.title = ascii
4028            ? "APPLE ASCII display: bit 7 stripped; click for raw byte display"
4029            : "Raw 8-bit byte display; click for APPLE ASCII";
4030        return ascii;
4031    }
4032
4033    this.serialTerminalDisplayToggle = function()
4034    {
4035        serialTerminalState.displayMode =
4036            serialTerminalState.displayMode==="ascii" ? "raw" : "ascii";
4037
4038        var terminal = serialTerminalLive();
4039        if(terminal) serialTerminalRenderTranscript(terminal);
4040        serialTerminalApplyDisplayMode();
4041        return serialTerminalState.displayMode;
4042    };
4043
4044    function serialTerminalCopyText()
4045    {
4046        var terminal = serialTerminalLive();
4047        if(!terminal || !terminal._o || !terminal._o.DOM) return "";
4048        // The toolbar pictogram is deliberately COPY ALL.  A user's explicit
4049        // selection remains available to the browser's normal Cmd/Ctrl-C.
4050        var output = terminal._o.DOM.output;
4051        return output.innerText!==undefined ? output.innerText : output.textContent;
4052    }
4053
4054    this.serialTerminalCopy = async function()
4055    {
4056        var text = serialTerminalCopyText();
4057        if(!text.length) return 0;
4058
4059        var copied = false;
4060        try
4061        {
4062            if(typeof(navigator)!=="undefined" && navigator.clipboard &&
4063               typeof(navigator.clipboard.writeText)==="function")
4064            {
4065                await navigator.clipboard.writeText(text);
4066                copied = true;
4067            }
4068        }
4069        catch(ignore) {}
4070
4071        if(!copied)
4072        {
4073            var area = document.createElement("textarea");
4074            area.value = text;
4075            area.setAttribute("readonly","");
4076            area.style.cssText = "position:fixed;left:-10000px;top:-10000px;";
4077            document.body.appendChild(area);
4078            area.select();
4079            try { copied = document.execCommand("copy"); } catch(ignore) {}
4080            document.body.removeChild(area);
4081        }
4082
4083        return copied ? text.length : 0;
4084    };
4085
4086    function serialTerminalApplyTheme()
4087    {
4088        var popup = document.getElementById("serialProTerminal_popup");
4089        if(!popup) return false;
4090
4091        var terminal = popup._terminal;
4092        var root = terminal && terminal._o && terminal._o.DOM
4093            ? terminal._o.DOM.root
4094            : document.getElementById("serialProTerminal_host");
4095        if(!root) return false;
4096
4097        var light = serialTerminalState.theme==="light";
4098        root.classList.toggle("serialpro_terminal_light",light);
4099        root.classList.toggle("dotmatrix_bck2",light);
4100
4101        var icon = popup.querySelector("[data-serial-terminal-theme]");
4102        if(icon)
4103        {
4104            icon.title = light
4105                ? "Switch terminal to dark mode"
4106                : "Switch terminal to light dot-matrix paper mode";
4107            icon.setAttribute("aria-label",icon.title);
4108        }
4109
4110        return light;
4111    }
4112
4113    this.serialTerminalThemeToggle = function()
4114    {
4115        serialTerminalState.theme =
4116            serialTerminalState.theme==="light" ? "dark" : "light";
4117
4118        serialTerminalApplyTheme();
4119
4120        var terminal = serialTerminalLive();
4121        if(terminal && terminal._o && terminal._o.DOM && terminal._o.DOM.input)
4122            terminal._o.DOM.input.focus();
4123
4124        return serialTerminalState.theme;
4125    };
4126
4127    this.serialTerminalHelp = function()
4128    {
4129        var terminal = serialTerminalLive();
4130        if(!terminal) return false;
4131
4132        terminal.output(
4133            "<b>Serial Pro terminal</b><br>"
4134            +"Type a line and press Enter to send its 8-bit bytes followed by CR ($0D).<br>"
4135            +"A bare Enter sends CR. Arrow Up/Down recalls terminal history; Escape clears the input line.<br>"
4136            +"Incoming bytes from the Serial Pro appear in this window.<br><br>"
4137            +"ASCII mode strips the Apple II text high bit, making PR# / LIST and printer streams directly readable; RAW preserves diagnostic byte notation.<br>"
4138            +"Select terminal output normally and use Cmd/Ctrl-C for that selection. The copy pictogram always copies the complete terminal transcript.<br>"
4139            +"A live stream continues while text is selected without forcing the view back to the newest output.<br>"            
4140            +"Use <i class=\"fa fa-adjust\"></i> to switch between dark terminal mode and light dot-matrix paper mode.<br>"
4141            +"Use <i class=\"fa fa-plug\"></i> to connect/disconnect an optional physical USB serial port through Web Serial.<br>"
4142            +"Physical RX/TX shares the same 6551 endpoint; modem inputs are diagnostic-only in Stage 3A.<br>"
4143            +"CPU sync makes serial timing follow CPU acceleration; uncheck it to keep the selected baud approximately at x1 timing.<br>"
4144            +"<i>Current stage:</i> cycle-paced 6551 character timing + Stage 3A physical byte transport;
4144 individual serial waveform edges remain deferred."
4145        );
4146        return true;
4147    };
4148
4149    this.serialTerminalToggle = function()
4150    {
4151        if(typeof(TERMINAL)!="function")
4152        {
4153            console.error("Serial Pro terminal requires COM_oTERM.js");
4154            return false;
4155        }
4156
4157        var slotN = serialTerminalSlotN();
4158        if(slotN===null) return false;
4159
4160        var io = apple2plus.hwObj().io;
4161        var slotID = io.slot2ID(slotN);
4162        var popup = serialTerminalPopup();
4163        var sameSlot = Number(popup.getAttribute("data-slotN"))===slotN;
4164
4165        if(popup.hidden===false && sameSlot)
4166        {
4167            oCOM.POPUP.off("serialProTerminal_popup");
4168            return false;
4169        }
4170
4171        // Reopening the same slot keeps the live oTERM instance and its DOM.
4172        if(sameSlot && popup._terminal)
4173        {
4174            oCOM.POPUP.on("serialProTerminal_popup");
4175            serialPhysicalUpdateButton();
4176            serialTerminalApplyDisplayMode();
4177            setTimeout(function(){ popup._terminal._o.DOM.input.focus(); },0);
4178            return true;
4179        }
4180
4181        var call = "apple2plus.hwObj().io.SLOT2obj("+slotN+")";
4182        popup.setAttribute("data-slotN",String(slotN));
4183        popup.innerHTML = ""
4184            + "<div class=\"com_popup_title\" style=\"height:30px;padding:0px 6px;display:flex;align-items:center;gap:6px;\">"
4185            + "  <b>Serial Pro #"+slotID+" terminal</b>"
4186            + "  <span style=\"flex:1 1 auto\"></span>"
4187            + "  <button class=\"appbut skinny\" type=button data-serial-terminal-display"
4188            + "          title=\"APPLE ASCII display: bit 7 stripped; click for raw byte display\""
4189            + "          onclick=\""+call+".serialTerminalDisplayToggle()\">ASCII</button>"
4190            + "  <button class=\"appbut skinny\" type=button title=\"Copy complete terminal transcript to clipboard\""
4191            + "          onmousedown=\"event.preventDefault()\""
4192            + "          onclick=\""+call+".serialTerminalCopy()\"><i class=\"fa fa-copy\"></i></button>"
4193            + "  <button class=\"appbut skinny\" type=button"
4194            + "          title=\"Connect physical USB serial port (Web Serial)\""
4195            + "          onclick=\""+call+".serialPhysicalToggle()\">"
4196            + "    <i class=\"fa fa-plug\" data-serial-webserial></i>"
4197            + "  </button>"
4198            + "  <button class=\"appbut skinny\" type=button"
4199            + "          title=\"Switch terminal to light dot-matrix paper mode\""
4200            + "          onclick=\""+call+".serialTerminalThemeToggle()\">"
4201            + "    <i class=\"fa fa-adjust\" data-serial-terminal-theme></i>"
4202            + "  </button>"
4203            + "  <button class=\"appbut skinny\" type=button title=\"Terminal help\""
4204            + "          onclick=\""+call+".serialTerminalHelp()\">?</button>"
4205            + "  <button class=\"appbut skinny\" type=button title=\"Clear terminal display\""
4206            + "          onclick=\""+call+".serialTerminalClear()\">CLEAR</button>"
4207            + "  <button class=\"appbut skinny\" type=button title=\"Close serial terminal\""
4208            + "          onclick=\"oCOM.POPUP.off('serialProTerminal_popup')\">X</button>"
4209            + "</div>"
4210            + "<div class=\"com_popup_body\">"
4211            + "  <div id=\"serialProTerminal_host\" class=\"serialpro_terminal_host\"></div>"
4212            + "</div>";
4213
4214        oCOM.POPUP.on("serialProTerminal_popup");
4215
4216        var terminal = new TERMINAL({
4217             "container":"serialProTerminal_host"
4218            ,"welcome":""
4219            ,"prompt":""
4220            ,"separator":">"
4221            ,"storageKey":"SerialProTerminal_"+slotID
4222            ,"preserveWhitespace":true
4223            ,"allowEmptyInput":true
4224        });
4225
4226        popup._terminal = terminal;
4227        serialTerminalProtectStreaming(terminal);
4228        serialTerminalApplyTheme();
4229        serialTerminalApplyDisplayMode();
4230        serialPhysicalUpdateButton();
4231
4232        terminal.onInput(function(command,parameters,commandLine,rawLine)
4233        {
4234            serialpro.serialTerminalQueueLine(rawLine);
4235            return true;
4236        });
4237
4238        var rows = serialTerminalState.transcript;
4239        if(rows.length)
4240        {
4241            serialTerminalRenderTranscript(terminal);
4242        }
4243        else
4244        {
4245            serialpro.serialTerminalConsoleWrite(
4246                "Serial terminal endpoint ready; 6551 ACIA Stage 2D + optional Web Serial Stage 3A.\n",
4247                {"source":"spterm"}
4248            );
4249        }
4250
4251        setTimeout(function(){ terminal._o.DOM.input.focus(); },0);
4252        return true;
4253    };
4254
4255    this.deviceToolRTCSet = function(controlID)
4256    {
4257        rtcUI.controlID = controlID;
4258
4259        var parsed = rtcUIParse(controlID,true);
4260        rtcUIUpdateSetButton(controlID,parsed.valid);
4261
4262        if(!parsed.valid) return false;
4263        rtc.weekdayOffset = 0;
4264        rtcRebase(parsed.date);
4265        rtcUIRefresh(controlID,false);
4266
4267        rtcUIFieldValidity(rtcUIElement(controlID,"rtc_date"),true);
4268        rtcUIFieldValidity(rtcUIElement(controlID,"rtc_time"),true);
4269        rtcUIUpdateSetButton(controlID,true);
4270        return true;
4271    };
4272
4273    this.deviceToolRTCHost = function(controlID)
4274    {
4275        rtcUI.controlID = controlID;
4276        rtc.weekdayOffset = 0;
4277        rtcRebase(new Date());
4278        rtcUIRefresh(controlID,false);
4279
4280        rtcUIFieldValidity(rtcUIElement(controlID,"rtc_date"),true);
4281        rtcUIFieldValidity(rtcUIElement(controlID,"rtc_time"),true);
4282        rtcUIUpdateSetButton(controlID,true);        
4283        return true;
4284    };
4285
4286    this.deviceToolSlotHTML = function(ctx)
4287    {
4288        ctx = ctx || {};
4289
4290        var toolboxID = ctx.toolboxID || ("device_tool_"+ctx.slotID);
4291        var slotID = ctx.slotID==null ? "?" : String(ctx.slotID);
4292        var slotN = Number(ctx.slotN);
4293        var values = rtcUIValues();
4294
4295        // Child controls must not use the "device_tool_" prefix: Apple2IO
4296        // reserves that prefix for whole toolbox containers and hides them
4297        // when switching the visible peripheral.
4298        var controlID = "spc_ctrl_"+slotID;
4299
4300
4301        var call = "apple2plus.hwObj().io.SLOT2obj("+slotN+")";
4302
4303
4304        rtcUI.controlID = controlID;
4305        rtcUI.refreshEvent = "SPC_RTC_monitoring_"+slotID;
4306        serialUI.controlID = controlID;
4307        serialUI.refreshEvent = "SPC_SERIAL_monitoring_"+slotID;
4308
4309        // The dashboard refresh sequencer is already paced by
4310        // EMU_DashboardRefresh_s.  Keep the event registered and let the card's
4311        // requested/validation state decide whether a visual update is made.
4312        oCOM.addRefreshEvent(
4313            function(){ serialpro.deviceToolRTCMonitoring(); },
4314            rtcUI.refreshEvent,
4315            true
4316        );
4317
4318        oCOM.addRefreshEvent(
4319            function(){ serialpro.deviceToolSerialMonitoring(); },
4320            serialUI.refreshEvent,
4321            true
4322        );
4323
4324        var syncClass = rtcUI.syncRequested ? "fa-stop-circle" : "fa-sync-alt";
4325        var configured = serialConfiguredState();
4326
4327        var baudOptions = "";
4328        for(var b=1;b<ACIA_BAUD_TABLE.length;b++)
4329        {
4330            var rate = ACIA_BAUD_TABLE[b];
4331            baudOptions += "<option value=\""+rate+"\""
4332                +(configured.baud===rate ? " selected" : "")+">"+rate+"</option>";
4333        }
4334
4335        function option(value,label,current)
4336        {
4337            return "<option value=\""+value+"\""
4338                +(String(value)===String(current) ? " selected" : "")+">"+label+"</option>";
4339        }
4340
4341        var dataOptions = ""
4342            + option(8,"8 BIT",configured.dataBits)
4343            + option(7,"7 BIT",configured.dataBits)
4344            + option(6,"6 BIT",configured.dataBits)
4345            + option(5,"5 BIT",configured.dataBits);
4346
4347        var parityOptions = ""
4348            + option("none","NONE",configured.parity)
4349            + option("odd","ODD",configured.parity)
4350            + option("even","EVEN",configured.parity)
4351            + option("mark","MARK",configured.parity)
4352            + option("space","SPACE",configured.parity);
4353
4354        var stopOptions = ""
4355            + option(1,"1 STOP",configured.stopBits)
4356            + option(1.5,"1.5 STOP",configured.stopBits)
4357            + option(2,"2 STOP",configured.stopBits);        
4358
4359        var rxClockChecked = configured.rxClockInternal ? " checked" : "";
4360        var dtrChecked = aciaDTRAsserted() ? " checked" : "";
4361        var rtsChecked = aciaRTSAsserted() ? " checked" : "";
4362        var cpuSyncChecked = state.cpuSync ? " checked" : "";
4363
4364        return ""
4365            + "<div class=toolbox id=\""+toolboxID+"\" hidden>"
4366            + " <div class=appbox style=\"box-sizing:border-box;text-align:left;height:76px;padding:3px 6px;display:flex;flex-direction:column;gap:2px;font-size:11px;white-space:nowrap;\">"
4367            + "  <div style=\"height:21px;
4367display:flex;align-items:center;gap:4px;\">"
4368            + "   <b style=\"display:inline-block;width:28px;\">RTC</b>"
4369            + "  <input id=\""+controlID+"_rtc_date\" type=text value=\""+values.date+"\" maxlength=10"
4370            + "         title=\"Date YYYY-MM-DD; edit and press SET\""
4371            + "         oninput=\""+call+".deviceToolRTCInput('"+controlID+"')\""
4372            + "         style=\"box-sizing:border-box;width:76px;height:19px;font:11px monospace;padding:0px 2px;border:1px solid transparent;\">"
4373            + "  <input id=\""+controlID+"_rtc_time\" type=text value=\""+values.time+"\" maxlength=8"
4374            + "         title=\"Time HH:MM:SS; edit and press SET\""
4375            + "         oninput=\""+call+".deviceToolRTCInput('"+controlID+"')\""
4376            + "         style=\"box-sizing:border-box;width:62px;height:19px;font:11px monospace;padding:0px 2px;border:1px solid transparent;\">"
4377            + "  <button class=\"appbut\" type=button style=\"text-align:center;margin-left:0px;padding:4px\" title=\"Date/time sync\">"
4378            + "    <i class=\"fa "+syncClass+"\" id=\""+controlID+"_rtc_monitoring\""
4379            + "       title=\""+(rtcUI.syncRequested ? "Stop date/time sync" : "Start date/time sync")+"\""
4380            + "       onclick=\""+call+".deviceToolRTCSyncToggle('"+controlID+"')\"></i>"
4381            + "  </button>"            
4382            + "  <button class=\"appbut skinny\" id=\""+controlID+"_rtc_set\" type=button title=\"Set RTC from fields\""
4383            + "          onclick=\""+call+".deviceToolRTCSet('"+controlID+"')\">SET</button>"
4384            + "  <button class=\"appbut skinny\" type=button title=\"Set RTC from browser host time\""
4385            + "          onclick=\""+call+".deviceToolRTCHost('"+controlID+"')\">HOST</button>"
4386            + "  <label title=\"Follow CPU speed; uncheck to keep selected baud approximately at x1 timing\" style=\"display:flex;align-items:center;gap:2px;margin-left:auto;\">"
4387            + "    <input id=\""+controlID+"_serial_cpu_sync\" type=checkbox"+cpuSyncChecked
4388            + "           onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','cpusync',this.checked)\">CPU sync"
4389            + "  </label>"
4390            + "  </div>"
4391
4392            + "  <div style=\"height:21px;display:flex;align-items:center;gap:4px;\">"
4393            + "   <b style=\"display:inline-block;width:28px;\">SER</b>"
4394            + "   <select id=\""+controlID+"_serial_baud\" title=\"6551 baud rate; physical Web Serial uses this rate on next connection\""
4395            + "           onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','baud',this.value)\""
4396            + "           style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+baudOptions+"</select>"
4397            + "   <select id=\""+controlID+"_serial_data\" title=\"6551 data word length\""
4398            + "           onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','data',this.value)\""
4399            + "           style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+dataOptions+"</select>"
4400            + "   <select id=\""+controlID+"_serial_parity\" title=\"6551 parity mode\""
4401            + "           onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','parity',this.value)\""
4402            + "           style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+parityOptions+"</select>"
4403            + "   <select id=\""+controlID+"_serial_stop\" title=\"6551 effective stop-bit selection; SBN=1 depends on word length/parity\""
4404            + "           onchange=\""+call+".deviceToolSerialSetting('"+controlID+"','stop',this.value)\""
4405            + "           style=\"height:19px;font:11px monospace;padding:0px 1px;\">"+stopOptions+"</select>"
4406            + "   <label title=\"Use transmit baud as the 6551 receiver clock; required by physical Web Serial\" style=\"display:flex;align-items:center;gap:2px;\">"
4407            + "    <input id=\""+controlID+"_serial_rxclock\" type=checkbox"+rxClockChecked
4408            + "           onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','rxclock',this.checked)\">RX=TX"
4409            + "   </label>"
4410            + "  </div>"
4411
4412            + "  <div style=\"height:21px;
4412display:flex;align-items:center;gap:4px;\">"
4413            + "   <b style=\"display:inline-block;width:28px;\">USB</b>"
4414            + "   <button class=\"appbut skinny\" id=\""+controlID+"_physical_toggle\" type=button"
4415            + "           title=\"Connect a physical USB serial adapter through Web Serial\""
4416            + "           onclick=\""+call+".deviceToolPhysicalToggle('"+controlID+"')\">CONNECT</button>"
4417            + "   <span id=\""+controlID+"_physical_status\" style=\"display:inline-block;width:41px;font-size:10px;color:#777;\">OFFLINE</span>"
4418            + "   <label title=\"6551 DTR output; synchronized live to Web Serial DTR when connected\" style=\"display:flex;align-items:center;gap:1px;\">"
4419            + "    <input id=\""+controlID+"_physical_dtr\" type=checkbox"+dtrChecked
4420            + "           onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','dtr',this.checked)\">DTR"
4421            + "   </label>"
4422            + "   <label title=\"Manual 6551 RTS output; selects non-IRQ transmit mode and synchronizes live to Web Serial RTS\" style=\"display:flex;align-items:center;gap:1px;\">"
4423            + "    <input id=\""+controlID+"_physical_rts\" type=checkbox"+rtsChecked
4424            + "           onchange=\""+call+".deviceToolSerialFlag('"+controlID+"','rts',this.checked)\">RTS"
4425            + "   </label>"
4426            + "   <span title=\"Physical Clear To Send (diagnostic only)\">CTS<span id=\""+controlID+"_physical_cts\" style=\"color:#999\">●</span></span>"
4427            + "   <span title=\"Physical Data Carrier Detect (diagnostic only)\">DCD<span id=\""+controlID+"_physical_dcd\" style=\"color:#999\">●</span></span>"
4428            + "   <span title=\"Physical Data Set Ready (diagnostic only)\">DSR<span id=\""+controlID+"_physical_dsr\" style=\"color:#999\">●</span></span>"
4429            + "   <span title=\"Physical Ring Indicator (diagnostic only)\">RI<span id=\""+controlID+"_physical_ri\" style=\"color:#999\">●</span></span>"
4430            + "   <span style=\"flex:1 1 auto\"></span>"
4431            + "   <button class=\"appbut skinny\" type=button title=\"Open serial terminal\""
4432            + "           onclick=\""+call+".serialTerminalToggle()\"><i class=\"fa fa-terminal\"></i></button>"
4433            + " </div>"
4434            + " </div>"
4435            + "</div>";
4436    };
4437
4438    function resetROMState()
4439    {
4440        state.romSelector = 0x00;
4441        releaseExpansionROM();
4442    }
4443
4444    this.syncClock = function(clockTick)
4445    {
4446        // Keep RTC wall-clock interrupt detection beside the existing
4447        // cycle-paced ACIA timing.  The RTC itself remains tied to host time;
4448        // clockTick is used only to throttle Date.now() sampling.
4449        var rtcResult = rtcSyncClock(clockTick);
4450        var aciaResult = aciaSyncClock(clockTick);
4451        return rtcResult || aciaResult;
4452    };
4453
4454    this.reset = function()
4455    {
4456        resetROMState();
4457        aciaHardwareReset();
4458        // Bytes already typed at the remote end remain on the simulated line.
4459        aciaPrimeReceiver();
4460    };
4461
4462    this.restart = function()
4463    {
4464        resetROMState();
4465        aciaHardwareReset();
4466        // Bytes already typed at the remote end remain on the simulated line.
4467        aciaPrimeReceiver();
4468    };
4469}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.