ac6502 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +139 -32
  2. package/dist/components/IO/ACIA.d.ts +76 -0
  3. package/dist/components/IO/ACIA.js +282 -0
  4. package/dist/components/IO/ACIA.js.map +1 -0
  5. package/dist/components/IO/Attachments/Attachment.d.ts +112 -0
  6. package/dist/components/IO/Attachments/Attachment.js +71 -0
  7. package/dist/components/IO/Attachments/Attachment.js.map +1 -0
  8. package/dist/components/IO/Attachments/JoystickAttachment.d.ts +53 -0
  9. package/dist/components/IO/Attachments/JoystickAttachment.js +90 -0
  10. package/dist/components/IO/Attachments/JoystickAttachment.js.map +1 -0
  11. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.d.ts +63 -0
  12. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +489 -0
  13. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -0
  14. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.d.ts +44 -0
  15. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/Attachments/KeypadAttachment.d.ts +47 -0
  18. package/dist/components/IO/Attachments/KeypadAttachment.js +141 -0
  19. package/dist/components/IO/Attachments/KeypadAttachment.js.map +1 -0
  20. package/dist/components/IO/Attachments/LCDAttachment.d.ts +110 -0
  21. package/dist/components/IO/Attachments/LCDAttachment.js +716 -0
  22. package/dist/components/IO/Attachments/LCDAttachment.js.map +1 -0
  23. package/dist/components/IO/Attachments/SNESAttachment.d.ts +85 -0
  24. package/dist/components/IO/Attachments/SNESAttachment.js +184 -0
  25. package/dist/components/IO/Attachments/SNESAttachment.js.map +1 -0
  26. package/dist/components/IO/Empty.d.ts +9 -0
  27. package/dist/components/IO/Empty.js +5 -7
  28. package/dist/components/IO/Empty.js.map +1 -1
  29. package/dist/components/IO/GPIOCard.d.ts +5 -5
  30. package/dist/components/IO/GPIOCard.js.map +1 -1
  31. package/dist/components/IO/RAMBank.d.ts +37 -0
  32. package/dist/components/IO/RAMBank.js +63 -0
  33. package/dist/components/IO/RAMBank.js.map +1 -0
  34. package/dist/components/IO/RTC.d.ts +107 -0
  35. package/dist/components/IO/RTC.js +483 -0
  36. package/dist/components/IO/RTC.js.map +1 -0
  37. package/dist/components/IO/Sound.d.ts +120 -0
  38. package/dist/components/IO/Sound.js +622 -0
  39. package/dist/components/IO/Sound.js.map +1 -0
  40. package/dist/components/IO/Storage.d.ts +74 -0
  41. package/dist/components/IO/Storage.js +409 -0
  42. package/dist/components/IO/Storage.js.map +1 -0
  43. package/dist/components/IO/Terminal.d.ts +19 -0
  44. package/dist/components/IO/Terminal.js +33 -0
  45. package/dist/components/IO/Terminal.js.map +1 -0
  46. package/dist/components/IO/VIA.d.ts +105 -0
  47. package/dist/components/IO/VIA.js +597 -0
  48. package/dist/components/IO/VIA.js.map +1 -0
  49. package/dist/components/IO/Video.d.ts +141 -0
  50. package/dist/components/IO/Video.js +630 -0
  51. package/dist/components/IO/Video.js.map +1 -0
  52. package/dist/components/Machine.d.ts +20 -24
  53. package/dist/components/Machine.js +249 -166
  54. package/dist/components/Machine.js.map +1 -1
  55. package/dist/index.js +28 -14
  56. package/dist/index.js.map +1 -1
  57. package/dist/lib.d.ts +16 -16
  58. package/dist/lib.js +32 -32
  59. package/dist/lib.js.map +1 -1
  60. package/dist/tests/IO/ACIA.test.d.ts +1 -0
  61. package/dist/tests/IO/ACIA.test.js +423 -0
  62. package/dist/tests/IO/ACIA.test.js.map +1 -0
  63. package/dist/tests/IO/Attachments/Attachment.test.d.ts +1 -0
  64. package/dist/tests/IO/Attachments/Attachment.test.js +339 -0
  65. package/dist/tests/IO/Attachments/Attachment.test.js.map +1 -0
  66. package/dist/tests/IO/Attachments/JoystickAttachment.test.d.ts +1 -0
  67. package/dist/tests/IO/Attachments/JoystickAttachment.test.js +126 -0
  68. package/dist/tests/IO/Attachments/JoystickAttachment.test.js.map +1 -0
  69. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.d.ts +1 -0
  70. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +779 -0
  71. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -0
  72. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.d.ts +1 -0
  73. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js +355 -0
  74. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js.map +1 -0
  75. package/dist/tests/IO/Attachments/KeypadAttachment.test.d.ts +1 -0
  76. package/dist/tests/IO/Attachments/KeypadAttachment.test.js +323 -0
  77. package/dist/tests/IO/Attachments/KeypadAttachment.test.js.map +1 -0
  78. package/dist/tests/IO/Attachments/LCDAttachment.test.d.ts +1 -0
  79. package/dist/tests/IO/Attachments/LCDAttachment.test.js +627 -0
  80. package/dist/tests/IO/Attachments/LCDAttachment.test.js.map +1 -0
  81. package/dist/tests/IO/Attachments/SNESAttachment.test.d.ts +1 -0
  82. package/dist/tests/IO/Attachments/SNESAttachment.test.js +331 -0
  83. package/dist/tests/IO/Attachments/SNESAttachment.test.js.map +1 -0
  84. package/dist/tests/IO/Empty.test.d.ts +1 -0
  85. package/dist/tests/IO/Empty.test.js +121 -0
  86. package/dist/tests/IO/Empty.test.js.map +1 -0
  87. package/dist/tests/IO/GPIOCard.test.js.map +1 -1
  88. package/dist/tests/IO/RAMBank.test.d.ts +1 -0
  89. package/dist/tests/IO/RAMBank.test.js +229 -0
  90. package/dist/tests/IO/RAMBank.test.js.map +1 -0
  91. package/dist/tests/IO/RTC.test.d.ts +1 -0
  92. package/dist/tests/IO/RTC.test.js +177 -0
  93. package/dist/tests/IO/RTC.test.js.map +1 -0
  94. package/dist/tests/IO/Sound.test.d.ts +1 -0
  95. package/dist/tests/IO/Sound.test.js +528 -0
  96. package/dist/tests/IO/Sound.test.js.map +1 -0
  97. package/dist/tests/IO/Storage.test.d.ts +1 -0
  98. package/dist/tests/IO/Storage.test.js +656 -0
  99. package/dist/tests/IO/Storage.test.js.map +1 -0
  100. package/dist/tests/IO/VIA.test.d.ts +1 -0
  101. package/dist/tests/IO/VIA.test.js +503 -0
  102. package/dist/tests/IO/VIA.test.js.map +1 -0
  103. package/dist/tests/IO/Video.test.d.ts +1 -0
  104. package/dist/tests/IO/Video.test.js +549 -0
  105. package/dist/tests/IO/Video.test.js.map +1 -0
  106. package/dist/tests/Machine.test.js +27 -42
  107. package/dist/tests/Machine.test.js.map +1 -1
  108. package/package.json +1 -1
  109. package/src/components/IO/{SerialCard.ts → ACIA.ts} +2 -2
  110. package/src/components/IO/{GPIOAttachments/GPIOAttachment.ts → Attachments/Attachment.ts} +2 -2
  111. package/src/components/IO/{GPIOAttachments/GPIOJoystickAttachment.ts → Attachments/JoystickAttachment.ts} +3 -3
  112. package/src/components/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.ts → Attachments/KeyboardEncoderAttachment.ts} +3 -3
  113. package/src/components/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.ts → Attachments/KeyboardMatrixAttachment.ts} +5 -5
  114. package/src/components/IO/{GPIOAttachments/GPIOKeypadAttachment.ts → Attachments/KeypadAttachment.ts} +3 -3
  115. package/src/components/IO/{GPIOAttachments/GPIOLCDAttachment.ts → Attachments/LCDAttachment.ts} +7 -7
  116. package/src/components/IO/{EmptyCard.ts → Empty.ts} +1 -1
  117. package/src/components/IO/{RAMCard.ts → RAMBank.ts} +8 -8
  118. package/src/components/IO/{RTCCard.ts → RTC.ts} +1 -1
  119. package/src/components/IO/{SoundCard.ts → Sound.ts} +2 -2
  120. package/src/components/IO/{StorageCard.ts → Storage.ts} +70 -73
  121. package/src/components/IO/{DevOutputBoard.ts → Terminal.ts} +2 -2
  122. package/src/components/IO/{GPIOCard.ts → VIA.ts} +64 -64
  123. package/src/components/IO/{VideoCard.ts → Video.ts} +1 -1
  124. package/src/components/Machine.ts +276 -176
  125. package/src/index.ts +34 -21
  126. package/src/lib.ts +16 -16
  127. package/src/tests/IO/{SerialCard.test.ts → ACIA.test.ts} +5 -5
  128. package/src/tests/IO/{GPIOAttachments/GPIOAttachment.test.ts → Attachments/Attachment.test.ts} +12 -12
  129. package/src/tests/IO/{GPIOAttachments/GPIOJoystickAttachment.test.ts → Attachments/JoystickAttachment.test.ts} +23 -23
  130. package/src/tests/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts → Attachments/KeyboardEncoderAttachment.test.ts} +4 -4
  131. package/src/tests/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts → Attachments/KeyboardMatrixAttachment.test.ts} +5 -5
  132. package/src/tests/IO/{GPIOAttachments/GPIOKeypadAttachment.test.ts → Attachments/KeypadAttachment.test.ts} +38 -38
  133. package/src/tests/IO/{GPIOAttachments/GPIOLCDAttachment.test.ts → Attachments/LCDAttachment.test.ts} +12 -12
  134. package/src/tests/IO/Empty.test.ts +143 -0
  135. package/src/tests/IO/{RAMCard.test.ts → RAMBank.test.ts} +33 -33
  136. package/src/tests/IO/{RTCCard.test.ts → RTC.test.ts} +6 -6
  137. package/src/tests/IO/{SoundCard.test.ts → Sound.test.ts} +6 -6
  138. package/src/tests/IO/{StorageCard.test.ts → Storage.test.ts} +34 -25
  139. package/src/tests/IO/{GPIOCard.test.ts → VIA.test.ts} +7 -7
  140. package/src/tests/IO/{VideoCard.test.ts → Video.test.ts} +13 -13
  141. package/src/tests/Machine.test.ts +31 -38
@@ -0,0 +1,716 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LCDAttachment = exports.LCD_CMD_SET_DRAM_ADDR = exports.LCD_CMD_SET_CGRAM_ADDR = exports.LCD_CMD_FUNCTION_LCD_2LINE = exports.LCD_CMD_FUNCTION = exports.LCD_CMD_SHIFT_RIGHT = exports.LCD_CMD_SHIFT_DISPLAY = exports.LCD_CMD_SHIFT = exports.LCD_CMD_DISPLAY_CURSOR_BLINK = exports.LCD_CMD_DISPLAY_CURSOR = exports.LCD_CMD_DISPLAY_ON = exports.LCD_CMD_DISPLAY = exports.LCD_CMD_ENTRY_MODE_SHIFT = exports.LCD_CMD_ENTRY_MODE_INCREMENT = exports.LCD_CMD_ENTRY_MODE = exports.LCD_CMD_HOME = exports.LCD_CMD_CLEAR = void 0;
4
+ const Attachment_1 = require("./Attachment");
5
+ /**
6
+ * HD44780 LCD Controller Emulation — GPIO Attachment
7
+ *
8
+ * Emulates a 16×2 (or configurable) character LCD with HD44780 controller
9
+ * connected via 8-bit parallel interface on a 65C22 VIA.
10
+ *
11
+ * Pin mapping (accent on VIA ports):
12
+ * Port B (D0–D7): 8-bit data bus
13
+ * Port A bit 5: RS (Register Select — 0 = command, 1 = data)
14
+ * Port A bit 6: RW (Read/Write — 0 = write, 1 = read)
15
+ * Port A bit 7: E (Enable — active-high strobe, latched on falling edge)
16
+ *
17
+ * Display modes:
18
+ * Standard character display with 5×8 pixel characters
19
+ * Supports CGRAM for up to 8 user-defined characters
20
+ *
21
+ * Output: pixel buffer (cols*(5+1)-1) × (rows*(8+1)-1) with values:
22
+ * -1 = no pixel (inter-character gap)
23
+ * 0 = pixel off
24
+ * 1 = pixel on
25
+ *
26
+ * Reference: vrEmuLcd by Troy Schrapel
27
+ * https://github.com/visrealm/vrEmuLcd
28
+ */
29
+ // ── HD44780 Command bit masks ──────────────────────────────────────
30
+ exports.LCD_CMD_CLEAR = 0x01;
31
+ exports.LCD_CMD_HOME = 0x02;
32
+ exports.LCD_CMD_ENTRY_MODE = 0x04;
33
+ exports.LCD_CMD_ENTRY_MODE_INCREMENT = 0x02;
34
+ exports.LCD_CMD_ENTRY_MODE_SHIFT = 0x01;
35
+ exports.LCD_CMD_DISPLAY = 0x08;
36
+ exports.LCD_CMD_DISPLAY_ON = 0x04;
37
+ exports.LCD_CMD_DISPLAY_CURSOR = 0x02;
38
+ exports.LCD_CMD_DISPLAY_CURSOR_BLINK = 0x01;
39
+ exports.LCD_CMD_SHIFT = 0x10;
40
+ exports.LCD_CMD_SHIFT_DISPLAY = 0x08;
41
+ exports.LCD_CMD_SHIFT_RIGHT = 0x04;
42
+ exports.LCD_CMD_FUNCTION = 0x20;
43
+ exports.LCD_CMD_FUNCTION_LCD_2LINE = 0x08;
44
+ exports.LCD_CMD_SET_CGRAM_ADDR = 0x40;
45
+ exports.LCD_CMD_SET_DRAM_ADDR = 0x80;
46
+ // ── Constants ──────────────────────────────────────────────────────
47
+ const CHAR_WIDTH_PX = 5;
48
+ const CHAR_HEIGHT_PX = 8;
49
+ const DDRAM_SIZE = 128;
50
+ const CGRAM_STORAGE_CHARS = 16;
51
+ const ROM_FONT_CHARS = 256 - CGRAM_STORAGE_CHARS;
52
+ const DEFAULT_CGRAM_BYTE = 0xAA;
53
+ const DATA_WIDTH_CHARS_1ROW = 80;
54
+ const DATA_WIDTH_CHARS_2ROW = 40;
55
+ const CURSOR_MASK = exports.LCD_CMD_DISPLAY_CURSOR_BLINK | exports.LCD_CMD_DISPLAY_CURSOR;
56
+ // DDRAM row start addresses for multi-row displays
57
+ const ROW_OFFSETS = [0x00, 0x40, 0x14, 0x54];
58
+ // ── VIA Port A pin positions ───────────────────────────────────────
59
+ const PIN_RS = 0x20; // bit 5
60
+ const PIN_RW = 0x40; // bit 6
61
+ const PIN_E = 0x80; // bit 7
62
+ // ── HD44780 ROM A00 (Japanese) font ────────────────────────────────
63
+ // 240 characters (indices 16–255), each stored as 5 bytes — one per
64
+ // column, MSB at top. First 16 slots reserved for CGRAM.
65
+ const FONT_A00 = [
66
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 16
67
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 17
68
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 18
69
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 19
70
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 20
71
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 21
72
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 22
73
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 23
74
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 24
75
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 25
76
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 26
77
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 27
78
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 28
79
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 29
80
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 30
81
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 31
82
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 32 - (space)
83
+ [0x00, 0x00, 0xf2, 0x00, 0x00], // 33 - !
84
+ [0x00, 0xe0, 0x00, 0xe0, 0x00], // 34 - "
85
+ [0x28, 0xfe, 0x28, 0xfe, 0x28], // 35 - #
86
+ [0x24, 0x54, 0xfe, 0x54, 0x48], // 36 - $
87
+ [0xc4, 0xc8, 0x10, 0x26, 0x46], // 37 - %
88
+ [0x6c, 0x92, 0xaa, 0x44, 0x0a], // 38 - &
89
+ [0x00, 0xa0, 0xc0, 0x00, 0x00], // 39 - '
90
+ [0x00, 0x38, 0x44, 0x82, 0x00], // 40 - (
91
+ [0x00, 0x82, 0x44, 0x38, 0x00], // 41 - )
92
+ [0x28, 0x10, 0x7c, 0x10, 0x28], // 42 - *
93
+ [0x10, 0x10, 0x7c, 0x10, 0x10], // 43 - +
94
+ [0x00, 0x0a, 0x0c, 0x00, 0x00], // 44 - ,
95
+ [0x10, 0x10, 0x10, 0x10, 0x10], // 45 - -
96
+ [0x00, 0x06, 0x06, 0x00, 0x00], // 46 - .
97
+ [0x04, 0x08, 0x10, 0x20, 0x40], // 47 - /
98
+ [0x7c, 0x8a, 0x92, 0xa2, 0x7c], // 48 - 0
99
+ [0x00, 0x42, 0xfe, 0x02, 0x00], // 49 - 1
100
+ [0x42, 0x86, 0x8a, 0x92, 0x62], // 50 - 2
101
+ [0x84, 0x82, 0xa2, 0xd2, 0x8c], // 51 - 3
102
+ [0x18, 0x28, 0x48, 0xfe, 0x08], // 52 - 4
103
+ [0xe4, 0xa2, 0xa2, 0xa2, 0x9c], // 53 - 5
104
+ [0x3c, 0x52, 0x92, 0x92, 0x0c], // 54 - 6
105
+ [0x80, 0x8e, 0x90, 0xa0, 0xc0], // 55 - 7
106
+ [0x6c, 0x92, 0x92, 0x92, 0x6c], // 56 - 8
107
+ [0x60, 0x92, 0x92, 0x94, 0x78], // 57 - 9
108
+ [0x00, 0x6c, 0x6c, 0x00, 0x00], // 58 - :
109
+ [0x00, 0x6a, 0x6c, 0x00, 0x00], // 59 - ;
110
+ [0x10, 0x28, 0x44, 0x82, 0x00], // 60 - <
111
+ [0x28, 0x28, 0x28, 0x28, 0x28], // 61 - =
112
+ [0x00, 0x82, 0x44, 0x28, 0x10], // 62 - >
113
+ [0x40, 0x80, 0x8a, 0x90, 0x60], // 63 - ?
114
+ [0x4c, 0x92, 0x9e, 0x82, 0x7c], // 64 - @
115
+ [0x7e, 0x90, 0x90, 0x90, 0x7e], // 65 - A
116
+ [0xfe, 0x92, 0x92, 0x92, 0x6c], // 66 - B
117
+ [0x7c, 0x82, 0x82, 0x82, 0x44], // 67 - C
118
+ [0xfe, 0x82, 0x82, 0x44, 0x38], // 68 - D
119
+ [0xfe, 0x92, 0x92, 0x92, 0x82], // 69 - E
120
+ [0xfe, 0x90, 0x90, 0x90, 0x80], // 70 - F
121
+ [0x7c, 0x82, 0x92, 0x92, 0x5e], // 71 - G
122
+ [0xfe, 0x10, 0x10, 0x10, 0xfe], // 72 - H
123
+ [0x00, 0x82, 0xfe, 0x82, 0x00], // 73 - I
124
+ [0x04, 0x82, 0x82, 0xfc, 0x00], // 74 - J
125
+ [0xfe, 0x10, 0x28, 0x44, 0x82], // 75 - K
126
+ [0xfe, 0x02, 0x02, 0x02, 0x02], // 76 - L
127
+ [0xfe, 0x40, 0x30, 0x40, 0xfe], // 77 - M
128
+ [0xfe, 0x20, 0x10, 0x08, 0xfe], // 78 - N
129
+ [0x7c, 0x82, 0x82, 0x82, 0x7c], // 79 - O
130
+ [0xfe, 0x90, 0x90, 0x90, 0x60], // 80 - P
131
+ [0x7c, 0x82, 0x8a, 0x84, 0x7a], // 81 - Q
132
+ [0xfe, 0x90, 0x98, 0x94, 0x62], // 82 - R
133
+ [0x62, 0x92, 0x92, 0x92, 0x8c], // 83 - S
134
+ [0x80, 0x80, 0xfe, 0x80, 0x80], // 84 - T
135
+ [0xfc, 0x02, 0x02, 0x02, 0xfc], // 85 - U
136
+ [0xf8, 0x04, 0x02, 0x04, 0xf8], // 86 - V
137
+ [0xfc, 0x02, 0x1c, 0x02, 0xfc], // 87 - W
138
+ [0xc6, 0x28, 0x10, 0x28, 0xc6], // 88 - X
139
+ [0xe0, 0x10, 0x0e, 0x10, 0xe0], // 89 - Y
140
+ [0x86, 0x8a, 0x92, 0xa2, 0xc2], // 90 - Z
141
+ [0x00, 0xfe, 0x82, 0x82, 0x00], // 91 - [
142
+ [0xa8, 0x68, 0x3e, 0x68, 0xa8], // 92 - yen
143
+ [0x00, 0x82, 0x82, 0xfe, 0x00], // 93 - ]
144
+ [0x20, 0x40, 0x80, 0x40, 0x20], // 94 - ^
145
+ [0x02, 0x02, 0x02, 0x02, 0x02], // 95 - _
146
+ [0x00, 0x80, 0x40, 0x20, 0x00], // 96 - `
147
+ [0x04, 0x2a, 0x2a, 0x2a, 0x1e], // 97 - a
148
+ [0xfe, 0x12, 0x22, 0x22, 0x1c], // 98 - b
149
+ [0x1c, 0x22, 0x22, 0x22, 0x04], // 99 - c
150
+ [0x1c, 0x22, 0x22, 0x12, 0xfe], // 100 - d
151
+ [0x1c, 0x2a, 0x2a, 0x2a, 0x18], // 101 - e
152
+ [0x10, 0x7e, 0x90, 0x80, 0x40], // 102 - f
153
+ [0x30, 0x4a, 0x4a, 0x4a, 0x7c], // 103 - g
154
+ [0xfe, 0x10, 0x20, 0x20, 0x1e], // 104 - h
155
+ [0x00, 0x22, 0xbe, 0x02, 0x00], // 105 - i
156
+ [0x04, 0x02, 0x22, 0xbc, 0x00], // 106 - j
157
+ [0xfe, 0x08, 0x14, 0x22, 0x00], // 107 - k
158
+ [0x02, 0x82, 0xfe, 0x02, 0x02], // 108 - l
159
+ [0x3e, 0x20, 0x18, 0x20, 0x1e], // 109 - m
160
+ [0x3e, 0x10, 0x20, 0x20, 0x1e], // 110 - n
161
+ [0x1c, 0x22, 0x22, 0x22, 0x1c], // 111 - o
162
+ [0x3e, 0x28, 0x28, 0x28, 0x10], // 112 - p
163
+ [0x10, 0x28, 0x28, 0x18, 0x3e], // 113 - q
164
+ [0x3e, 0x10, 0x20, 0x20, 0x10], // 114 - r
165
+ [0x12, 0x2a, 0x2a, 0x2a, 0x04], // 115 - s
166
+ [0x20, 0xfc, 0x22, 0x02, 0x04], // 116 - t
167
+ [0x3c, 0x02, 0x02, 0x04, 0x3e], // 117 - u
168
+ [0x38, 0x04, 0x02, 0x04, 0x38], // 118 - v
169
+ [0x3c, 0x02, 0x0c, 0x02, 0x3c], // 119 - w
170
+ [0x22, 0x14, 0x08, 0x14, 0x22], // 120 - x
171
+ [0x30, 0x0a, 0x0a, 0x0a, 0x3c], // 121 - y
172
+ [0x22, 0x26, 0x2a, 0x32, 0x22], // 122 - z
173
+ [0x00, 0x10, 0x6c, 0x82, 0x00], // 123 - {
174
+ [0x00, 0x00, 0xfe, 0x00, 0x00], // 124 - |
175
+ [0x00, 0x82, 0x6c, 0x10, 0x00], // 125 - }
176
+ [0x10, 0x10, 0x54, 0x38, 0x10], // 126 - ->
177
+ [0x10, 0x38, 0x54, 0x10, 0x10], // 127 - <-
178
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 128
179
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 129
180
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 130
181
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 131
182
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 132
183
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 133
184
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 134
185
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 135
186
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 136
187
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 137
188
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 138
189
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 139
190
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 140
191
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 141
192
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 142
193
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 143
194
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 144
195
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 145
196
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 146
197
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 147
198
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 148
199
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 149
200
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 150
201
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 151
202
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 152
203
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 153
204
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 154
205
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 155
206
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 156
207
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 157
208
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 158
209
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 159
210
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 160
211
+ [0x0e, 0x0a, 0x0e, 0x00, 0x00], // 161
212
+ [0x00, 0x00, 0xf0, 0x80, 0x80], // 162
213
+ [0x02, 0x02, 0x1e, 0x00, 0x00], // 163
214
+ [0x08, 0x04, 0x02, 0x00, 0x00], // 164
215
+ [0x00, 0x18, 0x18, 0x00, 0x00], // 165
216
+ [0x50, 0x50, 0x52, 0x54, 0x78], // 166
217
+ [0x20, 0x22, 0x2c, 0x28, 0x30], // 167
218
+ [0x04, 0x08, 0x1e, 0x20, 0x00], // 168
219
+ [0x18, 0x12, 0x32, 0x12, 0x1c], // 169
220
+ [0x12, 0x12, 0x1e, 0x12, 0x12], // 170
221
+ [0x12, 0x14, 0x18, 0x3e, 0x10], // 171
222
+ [0x10, 0x3e, 0x10, 0x14, 0x18], // 172
223
+ [0x02, 0x12, 0x12, 0x1e, 0x02], // 173
224
+ [0x2a, 0x2a, 0x2a, 0x3e, 0x00], // 174
225
+ [0x18, 0x00, 0x1a, 0x02, 0x1c], // 175
226
+ [0x10, 0x10, 0x10, 0x10, 0x10], // 176
227
+ [0x80, 0x82, 0xbc, 0x90, 0xe0], // 177
228
+ [0x08, 0x10, 0x3e, 0x40, 0x80], // 178
229
+ [0x70, 0x40, 0xc2, 0x44, 0x78], // 179
230
+ [0x42, 0x42, 0x7e, 0x42, 0x42], // 180
231
+ [0x44, 0x48, 0x50, 0xfe, 0x40], // 181
232
+ [0x42, 0xfc, 0x40, 0x42, 0x7c], // 182
233
+ [0x50, 0x50, 0xfe, 0x50, 0x50], // 183
234
+ [0x10, 0x62, 0x42, 0x44, 0x78], // 184
235
+ [0x20, 0xc0, 0x42, 0x7c, 0x40], // 185
236
+ [0x42, 0x42, 0x42, 0x42, 0x7e], // 186
237
+ [0x40, 0xf2, 0x44, 0xf8, 0x40], // 187
238
+ [0x52, 0x52, 0x02, 0x04, 0x38], // 188
239
+ [0x42, 0x44, 0x48, 0x54, 0x62], // 189
240
+ [0x40, 0xfc, 0x42, 0x52, 0x62], // 190
241
+ [0x60, 0x12, 0x02, 0x04, 0x78], // 191
242
+ [0x10, 0x62, 0x52, 0x4c, 0x78], // 192
243
+ [0x50, 0x52, 0x7c, 0x90, 0x10], // 193
244
+ [0x70, 0x00, 0x72, 0x04, 0x78], // 194
245
+ [0x20, 0xa2, 0xbc, 0xa0, 0x20], // 195
246
+ [0x00, 0xfe, 0x10, 0x08, 0x00], // 196
247
+ [0x22, 0x24, 0xf8, 0x20, 0x20], // 197
248
+ [0x02, 0x42, 0x42, 0x42, 0x02], // 198
249
+ [0x42, 0x54, 0x48, 0x54, 0x60], // 199
250
+ [0x44, 0x48, 0xde, 0x68, 0x44], // 200
251
+ [0x00, 0x02, 0x04, 0xf8, 0x00], // 201
252
+ [0x1e, 0x00, 0x40, 0x20, 0x1e], // 202
253
+ [0xfc, 0x22, 0x22, 0x22, 0x22], // 203
254
+ [0x40, 0x42, 0x42, 0x44, 0x78], // 204
255
+ [0x20, 0x40, 0x20, 0x10, 0x0c], // 205
256
+ [0x4c, 0x40, 0xfe, 0x40, 0x4c], // 206
257
+ [0x40, 0x48, 0x44, 0x4a, 0x70], // 207
258
+ [0x00, 0x54, 0x54, 0x54, 0x02], // 208
259
+ [0x1c, 0x24, 0x44, 0x04, 0x0e], // 209
260
+ [0x02, 0x14, 0x08, 0x14, 0x60], // 210
261
+ [0x50, 0x7c, 0x52, 0x52, 0x52], // 211
262
+ [0x20, 0xfe, 0x20, 0x28, 0x30], // 212
263
+ [0x02, 0x42, 0x42, 0x7e, 0x02], // 213
264
+ [0x52, 0x52, 0x52, 0x52, 0x7e], // 214
265
+ [0x20, 0xa0, 0xa2, 0xa4, 0x38], // 215
266
+ [0xf0, 0x02, 0x04, 0xf8, 0x00], // 216
267
+ [0x3e, 0x00, 0x7e, 0x02, 0x0c], // 217
268
+ [0x7e, 0x02, 0x04, 0x08, 0x10], // 218
269
+ [0x7e, 0x42, 0x42, 0x42, 0x7e], // 219
270
+ [0x70, 0x40, 0x42, 0x44, 0x78], // 220
271
+ [0x42, 0x42, 0x02, 0x04, 0x18], // 221
272
+ [0x40, 0x20, 0x80, 0x40, 0x00], // 222
273
+ [0xe0, 0xa0, 0xe0, 0x00, 0x00], // 223
274
+ [0x1c, 0x22, 0x12, 0x0c, 0x32], // 224
275
+ [0x04, 0xaa, 0x2a, 0xaa, 0x1e], // 225
276
+ [0x1f, 0x2a, 0x2a, 0x2a, 0x14], // 226
277
+ [0x14, 0x2a, 0x2a, 0x22, 0x04], // 227
278
+ [0x3f, 0x02, 0x02, 0x04, 0x3e], // 228
279
+ [0x1c, 0x22, 0x32, 0x2a, 0x24], // 229
280
+ [0x0f, 0x12, 0x22, 0x22, 0x1c], // 230
281
+ [0x1c, 0x22, 0x22, 0x22, 0x3f], // 231
282
+ [0x04, 0x02, 0x3c, 0x20, 0x20], // 232
283
+ [0x20, 0x20, 0x00, 0x70, 0x00], // 233
284
+ [0x00, 0x00, 0x20, 0xbf, 0x00], // 234
285
+ [0x50, 0x20, 0x50, 0x00, 0x00], // 235
286
+ [0x18, 0x24, 0x7e, 0x24, 0x08], // 236
287
+ [0x28, 0xfe, 0x2a, 0x02, 0x02], // 237
288
+ [0x3e, 0x90, 0xa0, 0xa0, 0x1e], // 238
289
+ [0x1c, 0xa2, 0x22, 0xa2, 0x1c], // 239
290
+ [0x3f, 0x12, 0x22, 0x22, 0x1c], // 240
291
+ [0x1c, 0x22, 0x22, 0x12, 0x3f], // 241
292
+ [0x3c, 0x52, 0x52, 0x52, 0x3c], // 242
293
+ [0x0c, 0x14, 0x08, 0x14, 0x18], // 243
294
+ [0x1a, 0x26, 0x20, 0x26, 0x1a], // 244
295
+ [0x3c, 0x82, 0x02, 0x84, 0x3e], // 245
296
+ [0xc6, 0xaa, 0x92, 0x82, 0x82], // 246
297
+ [0x22, 0x3c, 0x20, 0x3e, 0x22], // 247
298
+ [0xa2, 0x94, 0x88, 0x94, 0xa2], // 248
299
+ [0x3c, 0x02, 0x02, 0x02, 0x3f], // 249
300
+ [0x28, 0x28, 0x3e, 0x28, 0x48], // 250
301
+ [0x22, 0x3c, 0x28, 0x28, 0x2e], // 251
302
+ [0x3e, 0x28, 0x38, 0x28, 0x3e], // 252
303
+ [0x08, 0x08, 0x2a, 0x08, 0x08], // 253
304
+ [0x00, 0x00, 0x00, 0x00, 0x00], // 254
305
+ [0xff, 0xff, 0xff, 0xff, 0xff], // 255
306
+ ];
307
+ class LCDAttachment extends Attachment_1.AttachmentBase {
308
+ constructor(cols = 16, rows = 2, priority = 0) {
309
+ super(priority, false, false, false, false);
310
+ // ── HD44780 internal state ─────────────────────────────────────
311
+ this.entryModeFlags = exports.LCD_CMD_ENTRY_MODE_INCREMENT;
312
+ this.displayFlags = 0x00;
313
+ this.scrollOffset = 0;
314
+ /** Current DDRAM pointer offset */
315
+ this.ddPtr = 0;
316
+ /** Current CGRAM pointer (null when not in CGRAM mode) */
317
+ this.cgPtr = null;
318
+ // ── Cursor blink timing ────────────────────────────────────────
319
+ this.blinkAccumulator = 0;
320
+ this.blinkState = false;
321
+ // ── VIA bus latch state ────────────────────────────────────────
322
+ this.lastPortA = 0;
323
+ this.lastE = false;
324
+ this.lastPortBValue = 0;
325
+ this.cols = cols;
326
+ this.rows = rows;
327
+ this.ddRam = new Uint8Array(DDRAM_SIZE);
328
+ this.cgRam = new Uint8Array(CGRAM_STORAGE_CHARS * CHAR_WIDTH_PX);
329
+ this.dataWidthCols = rows <= 1 ? DATA_WIDTH_CHARS_1ROW : DATA_WIDTH_CHARS_2ROW;
330
+ this.pixelsWidth = cols * (CHAR_WIDTH_PX + 1) - 1;
331
+ this.pixelsHeight = rows * (CHAR_HEIGHT_PX + 1) - 1;
332
+ this.buffer = new Int8Array(this.pixelsWidth * this.pixelsHeight);
333
+ this.reset();
334
+ }
335
+ // ── Attachment interface ───────────────────────────────────
336
+ reset() {
337
+ super.reset();
338
+ this.entryModeFlags = exports.LCD_CMD_ENTRY_MODE_INCREMENT;
339
+ this.displayFlags = 0x00;
340
+ this.scrollOffset = 0;
341
+ this.ddRam.fill(0x20); // space
342
+ this.ddPtr = 0;
343
+ this.cgRam.fill(DEFAULT_CGRAM_BYTE);
344
+ this.cgPtr = null;
345
+ this.blinkAccumulator = 0;
346
+ this.blinkState = false;
347
+ this.lastPortA = 0;
348
+ this.lastE = false;
349
+ this.buffer.fill(-1);
350
+ this.updatePixels();
351
+ }
352
+ tick(cpuFrequency) {
353
+ // Advance cursor blink timer
354
+ const msPerTick = (128 / cpuFrequency) * 1000;
355
+ this.blinkAccumulator += msPerTick;
356
+ if (this.blinkAccumulator >= LCDAttachment.BLINK_PERIOD_MS) {
357
+ this.blinkAccumulator -= LCDAttachment.BLINK_PERIOD_MS;
358
+ this.blinkState = !this.blinkState;
359
+ }
360
+ }
361
+ /**
362
+ * Port A carries the control signals.
363
+ * We detect E falling edge to latch the bus.
364
+ */
365
+ writePortA(value, ddr) {
366
+ const maskedValue = value & ddr;
367
+ const currentE = !!(maskedValue & PIN_E);
368
+ const prevE = this.lastE;
369
+ // Latch on falling edge of E — use the PREVIOUS lastPortA so that
370
+ // RS/RW reflect the state while E was still HIGH (HD44780 setup-time
371
+ // requirement). If the CPU drops RS and E in the same VIA write,
372
+ // this preserves the RS value that was active during the E=1 phase.
373
+ if (prevE && !currentE) {
374
+ this.latchBus();
375
+ }
376
+ this.lastPortA = maskedValue;
377
+ this.lastE = currentE;
378
+ }
379
+ readPortA(ddr, or) {
380
+ return 0xFF;
381
+ }
382
+ readPortB(ddr, or) {
383
+ // If R/W is high (read mode), provide data on Port B
384
+ const portA = this.lastPortA;
385
+ if (portA & PIN_RW) {
386
+ if (portA & PIN_RS) {
387
+ // RS=1, RW=1 → Read data
388
+ return this.readByte();
389
+ }
390
+ else {
391
+ // RS=0, RW=1 → Read address / busy flag
392
+ return this.readAddress();
393
+ }
394
+ }
395
+ return 0xFF;
396
+ }
397
+ // ── Bus latch (E falling edge) ────────────────────────────────
398
+ latchBus() {
399
+ const portA = this.lastPortA;
400
+ const rw = !!(portA & PIN_RW);
401
+ const rs = !!(portA & PIN_RS);
402
+ if (rw) {
403
+ // Read operations are handled via readPortB
404
+ return;
405
+ }
406
+ // Write operation — capture data from Port B output register.
407
+ // Since we can't directly read the OR, the 6502 software must have
408
+ // written data to Port B *before* toggling E. We store it in writePortB
409
+ // — but the VIA card calls writePortB with the actual value. By the time
410
+ // E falls the data on the bus is the Port B output register value.
411
+ // We need the data value — it's available from the last writePortB call.
412
+ // However, writePortB doesn't store anything here. The VIA resolves
413
+ // the actual output from OR & DDR and passes it to us.
414
+ //
415
+ // For writes, the data on Port B is the value written by the CPU.
416
+ // The VIA will have called writePortB with the data already.
417
+ // We need to capture it — store it via writePortB override.
418
+ // Actually — we need to capture the Port B value. Let's store it.
419
+ const data = this.lastPortBValue;
420
+ if (rs) {
421
+ this.writeByte(data);
422
+ }
423
+ else {
424
+ this.sendCommand(data);
425
+ }
426
+ this.updatePixels();
427
+ }
428
+ writePortB(value, ddr) {
429
+ this.lastPortBValue = value & ddr;
430
+ }
431
+ // ── HD44780 Command Processing ────────────────────────────────
432
+ sendCommand(command) {
433
+ if (command & exports.LCD_CMD_SET_DRAM_ADDR) {
434
+ // Set DDRAM address — remaining 7 bits
435
+ this.ddPtr = command & 0x7F;
436
+ this.cgPtr = null;
437
+ }
438
+ else if (command & exports.LCD_CMD_SET_CGRAM_ADDR) {
439
+ // Set CGRAM address — remaining 6 bits
440
+ this.cgPtr = command & 0x3F;
441
+ }
442
+ else if (command & exports.LCD_CMD_FUNCTION) {
443
+ // Function set — we just acknowledge (8-bit mode, 2-line assumed)
444
+ }
445
+ else if (command & exports.LCD_CMD_SHIFT) {
446
+ if (command & exports.LCD_CMD_SHIFT_DISPLAY) {
447
+ // Shift entire display
448
+ if (command & exports.LCD_CMD_SHIFT_RIGHT) {
449
+ --this.scrollOffset;
450
+ }
451
+ else {
452
+ ++this.scrollOffset;
453
+ }
454
+ }
455
+ else {
456
+ // Shift cursor
457
+ if (command & exports.LCD_CMD_SHIFT_RIGHT) {
458
+ this.incrementDdPtr();
459
+ }
460
+ else {
461
+ this.decrementDdPtr();
462
+ }
463
+ }
464
+ }
465
+ else if (command & exports.LCD_CMD_DISPLAY) {
466
+ this.displayFlags = command;
467
+ }
468
+ else if (command & exports.LCD_CMD_ENTRY_MODE) {
469
+ this.entryModeFlags = command;
470
+ }
471
+ else if (command & exports.LCD_CMD_HOME) {
472
+ this.ddPtr = 0;
473
+ this.scrollOffset = 0;
474
+ }
475
+ else if (command === exports.LCD_CMD_CLEAR) {
476
+ this.ddRam.fill(0x20);
477
+ this.ddPtr = 0;
478
+ this.scrollOffset = 0;
479
+ this.entryModeFlags = exports.LCD_CMD_ENTRY_MODE_INCREMENT;
480
+ }
481
+ }
482
+ // ── Data Write / Read ─────────────────────────────────────────
483
+ writeByte(data) {
484
+ if (this.cgPtr !== null) {
485
+ // Write to CGRAM
486
+ const row = this.cgPtr % CHAR_HEIGHT_PX;
487
+ const charBase = this.cgPtr - row;
488
+ for (let i = 0; i < CHAR_WIDTH_PX; i++) {
489
+ const bit = data & ((0x01 << (CHAR_WIDTH_PX - 1)) >> i);
490
+ const addr = charBase * CHAR_WIDTH_PX / CHAR_HEIGHT_PX * CHAR_HEIGHT_PX;
491
+ // CGRAM is stored column-major like vrEmuLcd: cgRam[char][col]
492
+ // Each column byte has rows packed as bits (MSB = row 0)
493
+ const idx = Math.floor(this.cgPtr / CHAR_HEIGHT_PX) * CHAR_WIDTH_PX + i;
494
+ if (idx < this.cgRam.length) {
495
+ if (bit) {
496
+ this.cgRam[idx] |= (0x80 >> row);
497
+ }
498
+ else {
499
+ this.cgRam[idx] &= ~(0x80 >> row);
500
+ }
501
+ }
502
+ }
503
+ }
504
+ else {
505
+ // Write to DDRAM
506
+ if (this.ddPtr < DDRAM_SIZE) {
507
+ this.ddRam[this.ddPtr] = data;
508
+ }
509
+ }
510
+ this.doShift();
511
+ }
512
+ readByte() {
513
+ if (this.cgPtr !== null) {
514
+ const row = this.cgPtr % CHAR_HEIGHT_PX;
515
+ const charIdx = Math.floor(this.cgPtr / CHAR_HEIGHT_PX);
516
+ let data = 0;
517
+ for (let i = 0; i < CHAR_WIDTH_PX; i++) {
518
+ const idx = charIdx * CHAR_WIDTH_PX + i;
519
+ if (idx < this.cgRam.length && (this.cgRam[idx] & (0x80 >> row))) {
520
+ data |= ((0x01 << (CHAR_WIDTH_PX - 1)) >> i);
521
+ }
522
+ }
523
+ return data;
524
+ }
525
+ return this.ddPtr < DDRAM_SIZE ? this.ddRam[this.ddPtr] : 0x20;
526
+ }
527
+ readAddress() {
528
+ if (this.cgPtr !== null) {
529
+ return this.cgPtr & 0x3F;
530
+ }
531
+ return this.ddPtr & 0x7F;
532
+ }
533
+ // ── DDRAM pointer management ──────────────────────────────────
534
+ incrementDdPtr() {
535
+ this.ddPtr++;
536
+ if (this.rows > 1) {
537
+ if (this.ddPtr === 0x28) {
538
+ this.ddPtr = 0x40;
539
+ }
540
+ else if (this.ddPtr === 0x68 || this.ddPtr >= DDRAM_SIZE) {
541
+ this.ddPtr = 0x00;
542
+ }
543
+ }
544
+ else if (this.ddPtr >= 80) {
545
+ this.ddPtr = 0;
546
+ }
547
+ }
548
+ decrementDdPtr() {
549
+ this.ddPtr--;
550
+ if (this.rows > 1) {
551
+ if (this.ddPtr < 0) {
552
+ this.ddPtr = 0x67;
553
+ }
554
+ else if (this.ddPtr === 0x3F) {
555
+ this.ddPtr = 0x27;
556
+ }
557
+ }
558
+ else {
559
+ if (this.ddPtr < 0) {
560
+ this.ddPtr = 79;
561
+ }
562
+ }
563
+ }
564
+ doShift() {
565
+ if (this.cgPtr !== null) {
566
+ // Shift CGRAM pointer
567
+ if (this.entryModeFlags & exports.LCD_CMD_ENTRY_MODE_INCREMENT) {
568
+ this.cgPtr++;
569
+ if (this.cgPtr >= CGRAM_STORAGE_CHARS * CHAR_HEIGHT_PX) {
570
+ this.cgPtr = 0;
571
+ }
572
+ }
573
+ else {
574
+ this.cgPtr--;
575
+ if (this.cgPtr < 0) {
576
+ this.cgPtr = CGRAM_STORAGE_CHARS * CHAR_HEIGHT_PX - 1;
577
+ }
578
+ }
579
+ return;
580
+ }
581
+ // Shift display or cursor
582
+ if (this.entryModeFlags & exports.LCD_CMD_ENTRY_MODE_SHIFT) {
583
+ if (this.entryModeFlags & exports.LCD_CMD_ENTRY_MODE_INCREMENT) {
584
+ ++this.scrollOffset;
585
+ }
586
+ else {
587
+ --this.scrollOffset;
588
+ }
589
+ }
590
+ if (this.entryModeFlags & exports.LCD_CMD_ENTRY_MODE_INCREMENT) {
591
+ this.incrementDdPtr();
592
+ }
593
+ else {
594
+ this.decrementDdPtr();
595
+ }
596
+ }
597
+ // ── Character Data Lookup ─────────────────────────────────────
598
+ /**
599
+ * Get the 5-column font data for a character.
600
+ * Characters 0–15 come from CGRAM; 16–255 from ROM.
601
+ */
602
+ charBits(c) {
603
+ if (c < CGRAM_STORAGE_CHARS) {
604
+ // Return a slice of cgRam for this character
605
+ const start = c * CHAR_WIDTH_PX;
606
+ return this.cgRam.subarray(start, start + CHAR_WIDTH_PX);
607
+ }
608
+ return FONT_A00[c - CGRAM_STORAGE_CHARS];
609
+ }
610
+ // ── Data Offset Helper ────────────────────────────────────────
611
+ getDataOffset(row, col) {
612
+ if (row >= this.rows)
613
+ row = this.rows - 1;
614
+ // Normalize negative scroll offset
615
+ let scroll = this.scrollOffset;
616
+ while (scroll < 0) {
617
+ scroll += this.dataWidthCols;
618
+ }
619
+ const dataCol = (col + scroll) % this.dataWidthCols;
620
+ if (this.rows > 1) {
621
+ return ROW_OFFSETS[row] + dataCol;
622
+ }
623
+ return dataCol;
624
+ }
625
+ // ── Pixel Buffer Update ───────────────────────────────────────
626
+ updatePixels() {
627
+ var _a;
628
+ const displayOn = !!(this.displayFlags & exports.LCD_CMD_DISPLAY_ON);
629
+ // Determine cursor state
630
+ let cursorOn = this.displayFlags & CURSOR_MASK;
631
+ if (this.displayFlags & exports.LCD_CMD_DISPLAY_CURSOR_BLINK) {
632
+ if (this.blinkState) {
633
+ cursorOn &= ~exports.LCD_CMD_DISPLAY_CURSOR_BLINK;
634
+ }
635
+ }
636
+ for (let row = 0; row < this.rows; row++) {
637
+ for (let col = 0; col < this.cols; col++) {
638
+ // Top-left pixel for this character cell
639
+ const charTopLeftX = col * (CHAR_WIDTH_PX + 1);
640
+ const charTopLeftY = row * (CHAR_HEIGHT_PX + 1);
641
+ // DDRAM offset
642
+ const ddOffset = this.getDataOffset(row, col);
643
+ const charCode = (_a = this.ddRam[ddOffset]) !== null && _a !== void 0 ? _a : 0x20;
644
+ // Should we draw cursor here?
645
+ const drawCursor = cursorOn && (ddOffset === this.ddPtr) && this.cgPtr === null;
646
+ // Get font data
647
+ const bits = this.charBits(charCode);
648
+ // Render 5×8 character
649
+ for (let y = 0; y < CHAR_HEIGHT_PX; y++) {
650
+ for (let x = 0; x < CHAR_WIDTH_PX; x++) {
651
+ const pixelIdx = (charTopLeftY + y) * this.pixelsWidth + (charTopLeftX + x);
652
+ if (!displayOn) {
653
+ this.buffer[pixelIdx] = 0;
654
+ continue;
655
+ }
656
+ // Font data is column-major: bits[x] has row bits, MSB = row 0
657
+ let pixel = (bits[x] & (0x80 >> y)) ? 1 : 0;
658
+ // Cursor override
659
+ if (drawCursor) {
660
+ if ((cursorOn & exports.LCD_CMD_DISPLAY_CURSOR_BLINK) ||
661
+ ((cursorOn & exports.LCD_CMD_DISPLAY_CURSOR) && y === CHAR_HEIGHT_PX - 1)) {
662
+ pixel = 1;
663
+ }
664
+ }
665
+ this.buffer[pixelIdx] = pixel;
666
+ }
667
+ }
668
+ }
669
+ }
670
+ }
671
+ // ── Public Accessors (for rendering / debugging) ──────────────
672
+ /** Get the raw DDRAM contents */
673
+ getDDRam() {
674
+ return this.ddRam;
675
+ }
676
+ /** Get the current DDRAM address pointer */
677
+ getDDPtr() {
678
+ return this.ddPtr;
679
+ }
680
+ /** Get the display flags */
681
+ getDisplayFlags() {
682
+ return this.displayFlags;
683
+ }
684
+ /** Get the entry mode flags */
685
+ getEntryModeFlags() {
686
+ return this.entryModeFlags;
687
+ }
688
+ /** Get scroll offset */
689
+ getScrollOffset() {
690
+ return this.scrollOffset;
691
+ }
692
+ /** Get CGRAM pointer (null if not in CGRAM mode) */
693
+ getCGPtr() {
694
+ return this.cgPtr;
695
+ }
696
+ /** Read the text content of a specific display row */
697
+ getRowText(row) {
698
+ let text = '';
699
+ for (let col = 0; col < this.cols; col++) {
700
+ const offset = this.getDataOffset(row, col);
701
+ text += String.fromCharCode(this.ddRam[offset]);
702
+ }
703
+ return text;
704
+ }
705
+ /** Pixel state at a given coordinate: -1 (gap), 0 (off), 1 (on) */
706
+ pixelState(x, y) {
707
+ const offset = y * this.pixelsWidth + x;
708
+ if (offset >= 0 && offset < this.buffer.length) {
709
+ return this.buffer[offset];
710
+ }
711
+ return -1;
712
+ }
713
+ }
714
+ exports.LCDAttachment = LCDAttachment;
715
+ LCDAttachment.BLINK_PERIOD_MS = 350;
716
+ //# sourceMappingURL=LCDAttachment.js.map