Estex-DSS/keys.md
2026-06-18 20:52:50 +10:00

829 lines
54 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Промт для новой сессии: изучение и модификация клавиатурной подсистемы DSS
## Задача
Изучить и при необходимости модифицировать алгоритмы работы с клавиатурой,
реализованные в [DSS/KEYINTER.ASM](DSS/KEYINTER.ASM) — модуле, который
включается в ядро DSS через `INCLUDE` из [DSS/DSS-MAIN.ASM](DSS/DSS-MAIN.ASM)
(строка 402, сразу после таблицы `DSS_API_TABLE`). Это код для компьютера
**Sprinter** (Z84C15), работающего как PS/2-хост для AT-клавиатуры через
встроенный в Z84C15 SIO канал A.
Перед изменениями сверяй свои выводы с исходным кодом: в этом проекте уже
зафиксирована память о том, что **доверять разбору регистров/стека от
суб-агентов нельзя** — трассируй PUSH/POP/EX AF/EXX лично (см.
`[[verify-subagent-reg-flow-claims]]` в auto-memory).
---
## 1. Рабочее окружение
- **Основная директория проекта** (kernel + сборка):
`/Users/tolik/Documents/SP_Projects/ASM/GIT/Estex DSS`
- **Доп. директория** (архив/референс, не собирается):
`/Users/tolik/Documents/SP_Projects/ASM/Another` (подключена в
`Estex DSS.code-workspace` как второй корень)
- **Подмодуль git**: [Shared_Includes/](Shared_Includes/) — общие
константы/структуры/доки; ветка `main`, url `../Shared_Includes.git`.
- **Кодировка/переводы строк**: исходники в **CP866 (русский DOS)** с
**CRLF**. Это включает `DSS-MAIN.ASM`, `KEYINTER.ASM` и большинство
`Shared_Includes/constants/*.inc`. `grep -aI` нужен, если grep считает
файлы бинарными.
- **Ассемблер**: `sjasmplus --syntax=afw`. Сборка идёт через VS Code
([.vscode/tasks.json](.vscode/tasks.json)) — Makefile нет. Полезные
задачи: `Build SYSTEM.DOS`, `Build SYSTEM.DOS [NEW]`
(`--define INCREASE_BUILD` → инкремент `DSS/build.txt`),
`Build SYSTEM.EXE`, `Build SYS.EXE`, `Copy all files to MAME`
(вызывает [RUN/Image.sh](RUN/Image.sh) → `hdfmonkey put` в
`/Users/tolik/Documents/MAME/IMG/test_2g.img`), `Run MAME`.
- **Выходы сборки**:
`Build/DSS/SYSTEM.DOS` (ядро),
`Build/DSS/SYSTEM.EXE` (шелл),
`Build/DSS/SYS.EXE` (бутлоадер),
`Build/DSS-MAIN.LST` (листинг ~1 МБ),
`Build/Variables.inc` (экспортированные символы через `--exp`).
- **Версия**: `VERS=1, MODF=71`, билд из `DSS/build.txt` (сейчас 68) → "DSS 1.71.68".
Логика в [DSS/VERSION.INC](DSS/VERSION.INC) + LUA в
[Shared_Includes/LUA/Functions.lua](Shared_Includes/LUA/Functions.lua).
---
## 2. Архитектура DSS и место KEYINTER
### 2.1. Точка входа ядра — [DSS/DSS-MAIN.ASM](DSS/DSS-MAIN.ASM)
- `ORG 0`, ядро располагается с адреса #0000 на странице ядра.
- **RST-векторы** (DSS-MAIN.ASM около строк 30-310):
- `RST 00` — RETFAR / реентри для дочерних процессов
- `RST 08` (`ToBIOS`) — портал в BIOS в SLOT0
- `RST 10` (`ToDSS`) — DSS API gateway → `RST_10` диспетчер на строке 187
- `RST 18` — портал в страницу драйверов
- `RST 20`, `RST 28` — NOPS
- `RST 30` (`ToDSS.Mouse`) — мышиный API
- `RST 38` (IM 1) — `INTx38_Handler` на строке 275: сохраняет все регистры
(главный набор + альтернативный + IX/IY), вызывает `CALL KEYSCAN`,
затем `RST ToDSS.Mouse GetPackets`, затем `cursor_interrupt`, `RETI`.
- **Диспетчер `RST_10`** (строка 187): `LD H,high DSS_API_TABLE / LD L,C`
C=номер функции, индексирует таблицу адресов с раздельными байтами
(низкие байты строки 340-361, высокие 369-390).
- **Клавиатурные слоты в `DSS_API_TABLE`** (строки 344-345 / 373-374):
- `#30 WAITKEY`, `#31 SCANKEY`, `#32 ECHOKEY`, `#33 CTRLKEY`,
**`#34 NOPS`** (зарезервирован, не реализован — константа
`Dss.EDIT` есть, кода нет), `#35 K_CLEAR`, `#36 K_SETUP`, `#37 TESTKEY`.
- **INCLUDE KEYINTER.ASM** — строка 402.
- **Boot-флоу** `F_START` (строка 567): DEPLOY → `CALL KEYBOARD_INIT`
(строка 592) → PRINT_INIT → инициализация мыши → инициализация дисков →
`EI``CLEAR_BUFFER_AND_INIT_PROC`.
- **Карта памяти** ([DSS/DSS_MAP.TXT](DSS/DSS_MAP.TXT)):
- `#0000..#0038` — RESTARTS
- `#0200..#03FF``DSS_API_TABLE`
- `#0400..#043F`**SBUF** (буфер клавиатуры, 64 байта)
- `#0440..#0BD1` — код KEYINTER (~1936 байт)
- `#0BD2..#0EF5` — драйвер экрана (для совместного использования с
`Cursor_On/Off`)
### 2.2. Соседние модули
- [DSS/API.asm](DSS/API.asm) — агрегатор-INCLUDE для всех `DSS/API/*.asm`.
- [DSS/FS_Module.asm](DSS/FS_Module.asm) — `FS/FAT.asm` + `FS/CDFS.ASM`.
- [DSS/DOS_Proc.asm](DSS/DOS_Proc.asm) — DOS-уровневые помощники
(имена файлов, маски, `SET_FM`, `OPENDSK` и т.п.).
- [DSS/Procedures.asm](DSS/Procedures.asm) — общие хелперы
(`PUTCHAR` строка 103, `PUTCHAR.NO_SCROLL` строка 104, `MK_TIME` и др.).
- [DSS/DRV-MAIN.ASM](DSS/DRV-MAIN.ASM) — отдельная страница драйверов
(DISP/ENT-блок в `DSS-MAIN.ASM:695-699`), своя таблица RST по адресам
`#A0000..#A0038`. Клавиатура там НЕ живёт.
- [DSS/drivers/Input/MOUSE.ASM](DSS/drivers/Input/MOUSE.ASM) — мышь;
делит окно INT 38h с KEYINTER (сначала `KEYSCAN`, потом мышь).
- [DSS/defines.inc](DSS/defines.inc) — compile-time флаги (см. §5).
- [DSS/DSS_MACROSES.Z80](DSS/DSS_MACROSES.Z80) — макросы. Закомментированный
`BUFFER_KEYINTER` (строки 2-82) задокументирован как канонический layout
`SBUF/KEYFLAG/KEYCTRL/KEYFLG/SOUND_K`, но реальные определения сейчас
inline в `KEYINTER.ASM`. `SET_PAGE_X` (строки 137-143) — рабочий макрос
переключения страниц SLOT3.
### 2.3. Внешние символы, на которые ссылается KEYINTER
| Символ | Что | Где определён |
|--------|-----|---------------|
| `PUTCHAR`, `PUTCHAR.NO_SCROLL` | вывод символа | `DSS/Procedures.asm:103-104` |
| `LOCATE` | установка курсора | `DSS/API/Locate.asm:8` |
| `VMODE` | байт текущего видеорежима | `DSS/API/SetVMod.asm:90` |
| `BANKTBL` | таблица логическая→физическая страница | `DSS/DSS-MAIN.ASM:423` |
| `RST_10` | диспетчер DSS | `DSS/DSS-MAIN.ASM:187` |
| `ToBIOS`, `BIOS.*` | BIOS API | `Shared_Includes/constants/BIOS_equ.inc` |
| `ToDSS`, `Dss.*`, `DSS_Error.*` | DSS константы | `Shared_Includes/constants/dss_equ.inc` |
| `Z84.SIO.Ch_A.{Data,Ctrl}`, `SP_SND.Beeper`, `SLOT3`, `TXTPAGE` | железо | `Shared_Includes/constants/SP2000.inc` (модуль `Z84`, строки 2111-2127); часть EQU может приходить из внешнего SP2000-инклюда не из снимка репо — проверять `Build/Variables.inc` |
---
## 3. KEYINTER.ASM подробно
Файл — 1371 строка, CRLF, CP866. Заголовок R01 (10.02.2003, DNS — добавлен
курсор в `ECHOKEY`) и R02 (13.04.2023, BAO — исправлен stack overflow в
`K_CLEAR`).
### 3.1. Публичный API (вызывается через `RST 10` с `C=` номер функции)
| C | Метка | Строки | Назначение |
|---|-------|--------|------------|
| #30 | `WAITKEY` | 112-119 | Блокирующее ожидание. Выход: `A=E=ASCII`, `D=` поз.код, `B=KEYCTRL`, `C=KEYFLAG` |
| #31 | `SCANKEY` | 122-128 | Неблокирующее. `ZF=1` если буфер пуст |
| #32 | `ECHOKEY` | 195-212 | `SCANKEY` + программный курсор (`Cursor_On`/`Off`), эхо через `PUTCHAR.NO_SCROLL` |
| #33 | `CTRLKEY` | 383-391 | Прочесть `BC=(KEYFLAG)` без извлечения; `A=0` если буфер пуст, `A=#FF` если есть |
| #34 | (NOPS) | — | `Dss.EDIT` определён, но функция не реализована в v1.71 |
| #35 | `K_CLEAR` | 431-445 | Очистить буфер (`HEAD:=HOST`), потом `JP RST_10` для повторной диспетчеризации, если `B ∈ [#30..#34]`. Иначе `A=#01 INVALID_FUNCTION`, `CF=1` |
| #36 | `K_SETUP` | 934-... | Мультиплексор: см. §3.6 |
| #37 | `TESTKEY` | 393-408 | Peek текущей записи без сдвига `HOST`. `ZF=1` если пуст |
### 3.2. Кольцевой буфер
- **`SBUF`** (строка 14): `_mInfoALIGN 256,0` + `BLOCK 64,0` — 64 байта на
границе страницы. Поэтому high-byte адреса слота = `high SBUF`, а
`HEAD`/`HOST` — только low-byte (0..#3F).
- **`HEAD`** (строка 33) — указатель писателя.
- **`HOST`** (строка 34) — указатель читателя.
- **Слот = 4 байта**: `byte+0=E (ASCII)`, `byte+1=D (поз.код | #80)`,
`byte+2=B (KEYCTRL snapshot)`, `byte+3=C (KEYFLAG snapshot)`.
- Макс. 16 записей.
- Пуст: `HEAD == HOST`. Полон: `HEAD == (HOST-4) & #3F` (проверка
`PUTSYM` строки 450-453).
- **`GetSymAddr`** (строки 467-476) — общий помощник: `A := (HL)`,
`INC (HL)` ×4, `RES 6,(HL)` (wrap modulo 64), формирует
`HL = high SBUF : старый offset`.
- **`PUTSYM`** (448-464) — продьюсер. На переполнении прыгает в
`FULL_BF` (496-505): если `SF_BUFF` бит SOUND_K взведён — `BEEP`
с `DE=230, HL=50`, ключ отбрасывается.
- **`GETSYM`** (479-493) — консьюмер.
### 3.3. Байты состояния (адресуются через `LD IX,KEYFLAG` на строке 519)
**`KEYFLAG`** (offset 0, alias `K_LOCK`, строка 45, init `#02``INS_L` on):
| Бит | Имя | Значение |
|-----|-----|---------|
| 7 | `LANG_L` | язык RUS/LAT |
| 6 | `PAUSE_L` | Pause Lock |
| 5 | `RES5_L` | резерв (в комментариях помечено как историческое `X_SHIFT`) |
| 4 | `RES4_L` | резерв |
| 3 | `NUM_L` | Num Lock |
| 2 | `SCRL_L` | Scroll Lock |
| 1 | `INS_L` | Insert |
| 0 | `CAPS_L` | Caps Lock |
Переключается: `CAPS_X` 668-671, `LANG_X` 674-690, `INS_X` 693-696,
`NUM_X` 699-702, `PAUSE_X` 705-718, `SCL_X` 721-724.
**`KEYCTRL`** (offset 1, alias `K_SHIFT`, строка 56, init `#00`):
| Бит | Имя |
|-----|-----|
| 7 | `L_SHIFT` |
| 6 | `R_SHIFT` |
| 5 | `X_CTRL` (любой Ctrl) |
| 4 | `X_ALT` (любой Alt) |
| 3 | `L_CTRL` |
| 2 | `L_ALT` |
| 1 | `R_CTRL` |
| 0 | `R_ALT` |
Устанавливаются в `SHIFTS` (799-836) на нажатии, сбрасываются в `UNSHIFT`
(738-797) на отпускании. Также чистятся в `KBD_Receiver_Overrun` (1240).
**`KEY_FLG`** (offset 2, alias `KEYFLG`, строка 67, init `#00`):
| Бит | Имя |
|-----|-----|
| 7 | `FLAG_E0` — защёлкнут E0-префикс (extended) |
| 6 | `FLAG_F0` — защёлкнут F0-префикс (release) |
| 5 | `FLAG_E1` — E1-префикс (только если `USE_E1_SCANCODE`) |
| 4-1 | резерв |
| 0 | `CTRL_SHIFT` — Ctrl+Shift зафиксирован (для `LANG_X` при `CHANGE_LANG_CTRL_SHIFT`) |
**`SOUND_K`** (offset 3, строка 78, init `#03`оба звука включены):
| Бит | Имя |
|-----|-----|
| 7-2 | `FLAG_S7..S2`, резерв |
| 1 | `SF_ALT` — звук при смене языка |
| 0 | `SF_BUFF` — звук при переполнении буфера |
**`UNCODE`** (offset 4, WORD, строка 81) — последний отпущенный скан-код
(пишется в `UN_KEY` строка 663).
### 3.4. Таблица трансляции скан-кодов
- **`XLAT_T`** (строки 21-30) — сырая таблица 144 байта,
AT scancode (Set 2) → внутренний позиционный код 0..#5A.
`XLAT_T.Size = 144`. Bounds-check в `XLAT` на строке 860.
- **`XLAT`** (839-870) — собственно переводчик. Если защёлкнут `FLAG_E0`,
жёстко мапит специальные коды (`#11→#39 R-ALT`, `#14→#3A R-CTRL`,
`#5A→#4E numpad Enter`, `#4A→#4A /`, `#7C→#47 PrintScreen`); иначе
`L = XLAT_T[A]`.
### 3.5. ASCII-таблицы (`MODULE ASCII_TABLES`, строки 1285-1368)
Все таблицы ровно 90 байт (`Size EQU 90`):
- `ASCII_TABLES.ENGLISH` (1287-1295) — без шифта, англ
- `ASCII_TABLES.SHIFT_ENG` (1297-1305) — с шифтом, англ
- `ASCII_TABLES.CAPS_ENG` (1307-1315) — Caps, англ
- `ASCII_TABLES.CAPS_SHIFT_ENG` (1317-1325) — Caps+Shift, англ
- `ASCII_TABLES.RUSSIAN` (1328-1336) — без шифта, рус CP866
- `ASCII_TABLES.SHIFT_RUS` (1338-1346)
- `ASCII_TABLES.CAPS_RUS` (1348-1356)
- `ASCII_TABLES.CAPS_SHIFT_RUS` (1358-1366)
Старые метки `NORMTAB / SHIFTAB / CAPSTAB / SHF2TAB / NORMRUS / SHIFRUS
/ CAPSRUS / SHF2RUS` сохранились ТОЛЬКО в закомментированной legacy-копии
`K_SETUP` (строки 1097-1167) и **не являются живыми**. Все
KEYMAP/READMAP используют `ASCII_TABLES.*`.
### 3.6. K_SETUP подфункции (диспетчер на строке 934)
`INC B / DJNZ K_SND_R` — цепочка DJNZ:
| B | Метка | Поведение |
|---|-------|-----------|
| 0 | `KEYMAP` (937-972) / `READMAP` (974-1006) | Запись/чтение одной из 8 ASCII-таблиц. `HL=` буфер, `A bit7=0` запись, `bit7=1` чтение, биты0-2 = номер подтаблицы 0..7. Объём LDIR = `ASCII_TABLES.Size`=90 байт. Неверный A → `XOR A : SCF : RET` |
| 1 | `K_SND_R` (1019-1023) | `A := SOUND_K` |
| 2 | `K_SND_W` (1025-1030) | `SOUND_K := A` |
| 3 | `K_CURSOR_ON` (1034-1038) | Если `VMODE` показывает текстовый режим — `JP Cursor_On` |
| 4 | `K_CURSOR_OFF` (1042-1046) | То же → `JP Cursor_Off` |
| ≥5 | `K_SETUP.ERROR` (1015-1017) | `A = #0E INVALID_ACCESS, SCF, RET` |
### 3.7. Сканер клавиатуры — `KEYSCAN` (519-629)
- `LD IX,KEYFLAG` — IX указывает на блок состояния.
- **В текущем билде сканер ПОЛЛИНГ** (`KEYBOARD_INT_ENABLED=0`):
IF-блок строк 521-526 (который бы делал `CALL .RESCAN`, выход из INT)
НЕ ассемблируется. `KEYSCAN` падает прямо в `.RESCAN`. На практике он
вызывается из `INTx38_Handler` (`DSS-MAIN.ASM:275-306`), который сам
висит на `IM 1` `RST 38h`.
- **Логика `.RESCAN`** (от строки 527):
1. `IN A,(Z84.SIO.Ch_A.Ctrl)` — читаем RR0.
2. Если `bit0` (Rx Character Available) = 0 → `RET`.
3. Иначе пишем 1 в Ctrl (указать на регистр 1), читаем обратно,
тестируем `bit5` (Receiver Overrun) → ошибка → `JP KBD_Receiver_Overrun`
(1228) — дренаж FIFO, `OUT %00110000` в WR0 для error reset,
обнуление `KEYCTRL/KEY_FLG`.
4. Иначе `IN A,(Z84.SIO.Ch_A.Data)` (строка 538) — байт скан-кода.
5. Префиксы:
- `#F0``FULL_BF.F0_KEY`: устанавить `FLAG_F0`, `JP .RESCAN`.
- `#E0``FULL_BF.E0_KEY`: устанавить `FLAG_E0`, `JP .RESCAN`.
- `#E1` → если `USE_E1_SCANCODE=1` устанавить `FLAG_E1`,
иначе игнорировать.
6. Обычный байт + `FLAG_F0``UN_KEY` (657-664): `XLAT``UNSHIFT`
модификаторов, `UNCODE := HL` (последний отпущенный).
7. Обычный байт без `FLAG_F0``XLAT``SHIFTS`сброс `FLAG_E0/E1`
при `CHANGE_LANG_CTRL_SHIFT=1` опционально установить `CTRL_SHIFT`
`INPCODE``PUTSYM``JP .RESCAN` (продолжать дренировать FIFO,
строка 628) — поскольку SIO имеет **3-байтовый Rx FIFO** (см. §6.4).
### 3.8. INPCODE/RUSCODE/CONVERT (875-930)
- **`INPCODE`** (875-906) — выбор одной из 4 английских таблиц на основе
`KEYCTRL` бит шифта и `KEYFLAG.CAPS_L`. Если `LANG_L` взведён → `JP RUSCODE`.
- **`RUSCODE`** (908-930) — то же для русских.
- **`CONVERT`** (901-906): `SET 7,D` (флаг keystroke), `HL = таблица+L`,
`E := (HL)`. После этого `PUTSYM`.
### 3.9. Курсор и звук
- **`Cursor`** (215-218) — переключатель: `CPL` `.Flag`, falls into
`Cursor_On`.
- **`Cursor_On`** (220-253): `BIOS.LP_GET_PLACE` (#8E) → DE=Y/X, сохранить;
`BIOS.WIN_GET_SYM` (#B4); вычислить глиф — `NORM_ZG=#1B` обычно,
`INS_CUR_ZG=#9B` при `INS_L`, `CURSOR_ZG=#5B` для фазы blink;
`BIOS.WIN_PUT_SYM.NoChangeZG` (`#01B5`); `LOCATE`.
- **`Cursor_Off`** (257-284): обратное действие; `JP LOCATE`.
- **`cursor_interrupt`** (287-295): тик. Если `.Flag != 0`, декрементировать
таймер; на нуле `CALL Cursor`.
- **`SETUP_CURSORS`** (298-375): один раз при старте подгружает кастомные
глифы (#5B блок и #9B инверт.) в страницу шрифта (`BANKTBL+TXTPAGE`)
через `BIOS.WIN_GET_ZG` (#B8) → CPL → `BIOS.WIN_SET_ZG` (#B6).
- **`BEEP`** (1173-1192): `DE=` период, `HL=` повторы. Тогглит A=#10/#00 в
`(SP_SND.Beeper)` = порт #FE бит 4.
### 3.10. Инициализация — `KEYBOARD_INIT` (1194-1224)
Последовательность для SIO Ch_A (стандартная Z80 SIO init):
- `DI`
- WR0 := 0 (clear pointer)
- WR4 := парам. (clock, stop, parity) — точные байты в коде, ср. с
`Another/SPRINTER/TASM/Keybdrv.z80` (там 9 байт init)
- WR3 := `#C1` (Rx enable, 8 бит/символ)
- WR5 := `#62` (`0110'0010`: бит3 **Tx Enable = 0 → передатчик ВЫКЛЮЧЕН**; биты5-6 = 8 бит/символ). Передача данных клавиатуре аппаратно отключена — см. §6.5
- WR1 := `%00011001` если `KEYBOARD_INT_ENABLED=1`, иначе 0
- `EI : RET`
---
## 4. Конвенция вызова DSS API
- **DSS**: `LD C,Dss.Xxx` (номер функции из `Shared_Includes/constants/dss_equ.inc`,
модуль `Dss:`), `RST 10` (= `ToDSS`). Параметры — в регистрах согласно
спецификации (см. [Shared_Includes/Docs/DSS_1.71_Functions.asm](Shared_Includes/Docs/DSS_1.71_Functions.asm),
раздел 6).
- **Выход**: `CF=0` — успех (значения в регистрах согласно функции);
`CF=1` — ошибка, `A=` код ошибки (`DSS_Error.sys.*`).
- **BIOS**: `LD C,BIOS.XXX` (`Shared_Includes/constants/BIOS_equ.inc`,
модуль `BIOS:`), `RST 08` (= `ToBIOS`). Перед вызовом необходим `DI` и
`OUT (#7C),0` для переключения банка BIOS; стек должен быть в `#8000-#BFFF`.
Подробно — [Shared_Includes/Docs/BIOS functions.asm](Shared_Includes/Docs/BIOS%20functions.asm), строки 1-60.
- **Подфункции**: верхний байт BC (т.е. `B`) — индекс подфункции; константа
обычно собрана как `EQU subfn*256 + .Func`. Пример: `Dss.K_SETUP.SetSoundVars
EQU 2*256+#36`.
---
## 5. Compile-time флаги ([DSS/defines.inc](DSS/defines.inc))
| Флаг | Текущее | Что переключает в KEYINTER |
|------|---------|-----------------------------|
| `KEYBOARD_INT_ENABLED` (стр. 21) | `0` | `0` = поллинг из `INT 38h`; `1` = SIO Ch_A прерывание (WR1 := `#19`), отдельный обработчик в начале `KEYSCAN` |
| `CHANGE_LANG_CTRL_SHIFT` (стр. 22) | `1` | `1` = переключение языка Ctrl+Shift (через `CTRL_SHIFT` бит и `UNSHIFT.CHECK_CTRL_SHIFT`); `0` = Ctrl+Space (legacy путь `IFN CHANGE_LANG_CTRL_SHIFT` строки 585-591) |
| `BREAK_PROCESS_CODE` (стр. 23) | `#AC00` | Cooked-код Ctrl+C для `WaitKey`/`EDIT` → `DSS_Error.sys.USER_ABORT` (#25) |
| `USE_E1_SCANCODE` (стр. 24) | `0` | `1` = обрабатывать E1 (Pause/Break), `0` = игнорировать |
| `ENABLED_KEYBOARD_CONTROL` (стр. 24) | `0` | `1` = компилировать `FAKE_SHIFT` (игнор AT fake-shift `E0 12`/`E0 59` вокруг серых nav-клавиш). `0` = выключено: на железе SIO Tx отключён (WR5=`#62`), хост не может слать `0xED`, клавиатура не знает NumLock → fake-shift не приходит. Включать, только если появится двунаправленный канал к клавиатуре. См. §6.5 |
| `SHORT_RSTx10_TABLE` (стр. 10) | `0` | Должен быть `0` (либо ≥#37), иначе клавиатурные слоты не войдут в API table |
| `COMPILE_UNUSED_CODE` (стр. 11) | `0` | Гейтит legacy/debug куски |
Также в `defines.inc` — мышиные флаги (`MOUSE_INT_ENABLED`, ...) которые
делят с клавиатурой инфраструктуру SIO/IRQ.
---
## 6. Аппаратура Sprinter / Z84C15
### 6.1. Карта внутренних портов Z84C15
`Shared_Includes/constants/SP2000.inc`, `MODULE Z84` (строки 2111-2127):
| Порт | Назначение |
|------|------------|
| `#10..#13` | `CTC.Ch_0..3` (таймеры) |
| **`#18`** | `Z84.SIO.Ch_A.Data`**PS/2 клавиатура DAT_A** |
| **`#19`** | `Z84.SIO.Ch_A.Ctrl`**PS/2 клавиатура COM_A** |
| `#1A` | `SIO.Ch_B.Data` (DMOUSE) |
| `#1B` | `SIO.Ch_B.Ctrl` (CMOUSE) |
| `#1C..#1D` | `PIO.Port_A` — LPT1 (принтер) |
| `#1E..#1F` | `PIO.Port_B` — LPT2 |
| `#F0..#F1` | WDT |
| `#F4` | `IntPrior_Reg` |
| `#EE/#EF` | `SYS.Control / SYS.Data` |
Так что коммит `e334545 "M_INT: RFI is CH-A ONLY (per Z80 SIO spec)"`
относится к SIO Ch_A (клавиатура) — принтер на PIO, не на SIO.
### 6.2. Порт бипера и банкование
- `SP_SND.Beeper` = порт `#FE`, бит 4 (`%00010000`). Прямая запись из
KEYINTER (нет BIOS-функции для звука).
- `SLOT0..SLOT3` = `#82, #A2, #C2, #E2` — внешние порты переключения
страниц памяти на окна `#0000/#4000/#8000/#C000`. `SET_PAGE_X` из
`DSS_MACROSES.Z80` (137-143) использует SLOT3 + `BANKTBL[page]`.
- `TXTPAGE` — индекс в `BANKTBL` для страницы шрифта (используется в
`SETUP_CURSORS` строка 304). Если EQU не определён в снимке репо,
он приходит из внешнего SP2000-инклюда (см. `Build/Variables.inc`
после первой сборки).
### 6.3. Прерывания
- IM 1, единый вектор `#FF` для **кадрового И клавиатурного** прерывания.
- Различие: после входа в `INT 38h` ([DSS-MAIN.ASM:275](DSS/DSS-MAIN.ASM)),
читать порт `#19` (SIO Ch_A Ctrl/RR0); `bit0=1` → пришёл байт от
клавиатуры (в FIFO). Это и делает `KEYSCAN.RESCAN`.
- SIO Ch_A имеет **Rx FIFO на 3 байта**`sp2000.pdf §9.4` явно
предписывает: «после обработки одного байта проверять, не пришло ли
ещё, и возвращаться только если FIFO пуст».
- Включение режима, при котором клавиатурный INT работает:
`Port_All_Mode` = `#204E`, биты `SPECTRUM_MODE_OFF=1` (бит 0) и
`STOP_KBD_INT_OFF=8` (бит 3) (см. `SP2000.inc`).
- Активный указатель ISR хранится в `SYS_PAGE.INT_ADRESS = #FEC124`,
`INT_PAGE = #FEC126`, `INT_ID = #FEC127` (сентинель `#AA` — установлено).
### 6.4. Z80 SIO регистры (для модификаций инициализации)
- **WR0** — command + pointer на следующий WR/RR.
- **WR1** — interrupt enable (бит 0 ExtINT, биты 1-2 Tx Int, биты 3-4 Rx Int
mode `%01100` = INT_ON_RXBYTE), бит 5 Wait/Ready.
- **WR2** — Interrupt vector (только Ch_B по Z80 SIO spec — нерелевантно
для клавиатуры).
- **WR3** — Rx control: bit0 Rx Enable, биты 6-7 Rx bits/character.
- **WR4** — clock multiplier, stop bits, parity.
- **WR5** — Tx control: бит 3 Tx Enable, биты 5-6 Tx bits/character.
- **RR0** — статус: **бит 0 = Rx Character Available** (то что тестирует
KEYINTER), бит 1 = Int Pending (CH-A only), бит 2 = Tx Buffer Empty,
бит 5 = Sync/Hunt, бит 6 = CTS, бит 7 = Break.
- **RR1** — special Rx conditions: framing/parity/overrun errors.
- Полный datasheet — Zilog Z80 SIO Technical Manual (UM008); в репо его
нет, см. ссылки в §8.
### 6.5. Команды PS/2 хост→клавиатура (host commands)
Если потребуется управлять светодиодами Caps/Num/Scroll или autorepeat,
то протокол описан в [/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/KBD.TXT](/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/KBD.TXT):
- `#FF` reset+self-test, `#FE` resend, `#F4` enable scanning, `#F5` disable+default,
- `#ED` set LEDs (bit0 Scroll, bit1 Num, bit2 Caps),
- `#F3` set typematic rate+delay (биты 6-5 delay 250/500/750/1000 ms,
биты 4-0 rate 6..30 cps),
- `#F2` read keyboard ID,
- `#FB-#FD`, `#F7-#FA` per-key/all-keys typematic.
**Передача клавиатуре аппаратно невозможна в текущей конфигурации**: `KEYBOARD_INIT`
ставит WR5 = `#62` (бит3 Tx Enable = 0), т.е. передатчик SIO Ch_A выключен.
Поэтому host-команды (`0xED` Set LEDs, autorepeat и др.) не отправляются и не могут.
Важное следствие про **fake-shift**: по стандарту AT/PS-2 клавиатура шлёт fake-shift
(`E0 12` / `E0 59` вокруг серых extended nav-клавиш — стрелки/Ins/Del/Home/End/PgUp/PgDn)
ТОЛЬКО когда её внутренний Num Lock-LED включён. А LED выставляет ТОЛЬКО хост командой
`0xED` (нажатие Num Lock для клавиатуры — обычный скан-код `0x77`, своё состояние она не
меняет). Раз `0xED` послать нельзя → клавиатура всегда считает Num Lock выключенным →
**fake-shift на реальном железе не генерируется**, выделенные стрелки всегда приходят
чисто (`E0 6B` и т.п.). Обработка fake-shift реализована в `FAKE_SHIFT`, но компилируется
только при `ENABLED_KEYBOARD_CONTROL=1` (§5) — на железе это мёртвый код, нужен лишь для
эмуляторов (MAME msnat сам моделирует Num Lock+fake-shift, чего на железе быть не может).
Чтобы реально слать команды — включить Tx (WR5 бит3=1) и писать в `#18` после проверки
готовности Tx Buffer (RR0 bit2).
### 6.6. CMOS-привязка
- `CMOS_CELL.BootUpParams` = `#0E`, `.Mask.Language = %0000'0100` — пресет
языка при загрузке.
- `CMOS_CELL.Typematic` = `#0F`, маски `.Enabled %1000'0000`,
`.Delay %0110'0000`, `.Rate %0000'0111`.
- Доступ через `BIOS.CMOS_RD (#F6) / CMOS_WR (#F7)` или прямые порты
`CMOS.Port.{Data.Read=#FFBD, Data.Write=#BFBD, Address.Write=#DFBD}`.
- **Сейчас KEYINTER НЕ читает CMOS** — состояние локов и языка
сбрасывается на каждый boot к дефолтам. Если нужна persistence,
добавлять в `KEYBOARD_INIT` или в высокоуровневый `AUTOEXEC.BAT`.
---
## 7. Reference материалы
### 7.1. В репозитории
| Документ | Что искать |
|----------|------------|
| [Shared_Includes/Docs/sp2000.pdf](Shared_Includes/Docs/sp2000.pdf) | §1.1 Sp2000 hardware (p.4), §1.3 block diagram (p.5), §9 Внутренние порты Z84C15 (p.21), **§9.4 AT-Клавиатура p.25-26** (COM_A/DAT_A, sample code), §13.2 карта портов p.31 |
| [Shared_Includes/Docs/BIOS functions.asm](Shared_Includes/Docs/BIOS%20functions.asm) | Раздел 5 «Функции печати и управления режимом экрана» (стр. 628-1100): `LP_PRINT_SYM #82`, `LP_PRINT_ALL #81`, `LP_SET_PLACE #84`, `LP_GET_PLACE #8E`, `WIN_GET_SYM #B4`, `WIN_PUT_SYM #B5`, `WIN_GET_ZG #B8`, `WIN_SET_ZG #B6`, `LP_SCROLL_UD #8A`, `LP_PR_LINE_DIR #E0`. Конвенция вызова — стр. 1-60 |
| [Shared_Includes/Docs/DSS_1.71_Functions.asm](Shared_Includes/Docs/DSS_1.71_Functions.asm) | **Раздел 6 «Функции ввода с клавиатуры» (строки ~364-446)** — авторитетная спецификация на функции #30..#37 с корректировками из последней сессии (Pause Lock bit6, CTRLKey A=#FF/A=0, K_CLEAR INVALID_FUNCTION, K_SETUP B=0 это Get/Set table transfer) |
| [Shared_Includes/constants/dss_equ.inc](Shared_Includes/constants/dss_equ.inc) | `Dss.WaitKey..TestKey` (137-149), `Dss.K_SETUP.*` (144-148), `DSS_Error.sys.*` (280-336), **Приложение А — позиционные коды клавиш** (547-595) |
| [Shared_Includes/constants/BIOS_equ.inc](Shared_Includes/constants/BIOS_equ.inc) | `BIOS.*` функции (см. §7.1 ниже) |
| [Shared_Includes/constants/SP2000.inc](Shared_Includes/constants/SP2000.inc) | `MODULE Z84` (2111-2127), `SP_SND`, `ACEX.*`, `ZXKeys.Line_*` (#FEFE..#7FFE legacy ZX), `Port_All_Mode #204E`, `SYS_PAGE.INT_*`, `CMOS_CELL.*` |
| [Shared_Includes/constants/zx_char_codes.inc](Shared_Includes/constants/zx_char_codes.inc) | Control codes ZX: `.cursor_left=#08 (backspace)`, `.right=#09 (== TAB!)`, `.cursor_down=#0A`, `.cursor_up=#0B`, `.delete_left=#0C`, `.carriage_return=#0D`, и т.п. |
| [Shared_Includes/constants/dss_errors.z80](Shared_Includes/constants/dss_errors.z80) | Строки ошибок: #01 Invalid function, #0E Unknown operation, #13 Access denied, #20 Operation not supported, #25 User abort, #27 Unexpected app termination |
| [DSS/DSS_MAP.TXT](DSS/DSS_MAP.TXT) | Карта памяти ядра — #0400-#043F SBUF, #0440-#0BD1 KEYBOARD DRIVER |
| [DSS/CHANGES.LOG](DSS/CHANGES.LOG) | Хронология; ECHOKEY cursor 2003-02-10, txt 40x32 cursor bug, OVR cursor square |
| [DSS/KNOWN.BUG](DSS/KNOWN.BUG) | §3.3-3.4 — legacy ECHOKEY/PUTCHAR закомментированы, можно убирать; буферы #100/#80 без bounds-check |
### 7.2. В `Another/` (референсы, не собираются)
| Файл | Зачем |
|------|-------|
| [/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Sprinter_Programming.pdf](/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Sprinter_Programming.pdf) | **p.22 «Прерывания»** (вектор #FF, бит 0 порта #19), **p.25-26 «Клавиатура»** (пример K_SETUP layout-table, 9×16=144 байта на подтаблицу, Ctrl+Space layout switch), p.27 коды ошибок |
| [/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/KBD.TXT](/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/KBD.TXT) | PS/2 host→keyboard команды (см. §6.5) |
| [/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/AT-Keyboard.pdf](/Users/tolik/Documents/SP_Projects/ASM/Another/Docs/Keyboard/AT-Keyboard.pdf) | Scan Code Set 2 на 3 страницах |
| [/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Keybdrv.z80](/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Keybdrv.z80) | Минималистский standalone-драйвер клавиатуры Дениса Паринова (517 строк): полная init-последовательность 9 байт WR0..WR5 на ComA=#19, чтение DataA=#18. Полезно при отладке железа отдельно от DSS |
| [/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Scantabl.z80](/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Scantabl.z80), [Scantabl2.z80](/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/TASM/Scantabl2.z80) | Альтернативные scancode→symbol таблицы (для сверки `XLAT_T`) |
| [/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/SRC/cpm3-s/SCAN.PLM](/Users/tolik/Documents/SP_Projects/ASM/Another/SPRINTER/SRC/cpm3-s/SCAN.PLM) | CP/M-3 Sprinter scan-логика на PL/M (третий независимый референс) |
| **НЕ нужно**: `Another/scancode.inc` | NASM/x86 BIOS INT16h (Sergey Kiselev Micro-8088). Другая архитектура, пропустить |
### 7.3. Внешние (нужно скачать)
- **Zilog Z80 SIO Technical Manual (UM008)** — главы 3-4: полный layout
`WR0..WR7` / `RR0..RR2`. `sp2000.pdf §9` явно отсылает туда («Ссылка
на описание PIO, SIO и т.д. от Zilog»).
- **Zilog Z84C15 Product Specification** — карта внутренних портов,
CTC/PIO timing.
---
## 8. История изменений KEYINTER (git log)
`git log --oneline -- DSS/KEYINTER.ASM`:
- **9235af1** RUS keys fix
- **f5eacf4** KEYINTER.ASM: russian chars fix
- **e334545** M_INT: RFI is CH-A ONLY (per Z80 SIO spec) — **сохранять**
CH-A-only RFI семантику при правках ISR
- **73c7ab5** K_SETUP show/hide cursor sub-fns
- **12c0287** ECHOKEY scroll fix
- **eae2582** PutChar via BIOS LP_PR_LINE_DIR (#E0)
- **23fa77a** XLAT_T overflow + Ctrl+C
- **ff2f2b9** layout-switch combo (Ctrl+Space vs Shift+Ctrl) compile flag
- **54b1e80** SIO experiments
При работе всегда смотреть `git log -p DSS/KEYINTER.ASM | head -200`
часто старые комментарии R-номерами (R01, R02 ...) поясняют исторические
правки.
---
## 9. Полезные команды на хост-системе
- **Сборка ядра**: VS Code task `Build SYSTEM.DOS` (или `[NEW]` с
инкрементом билда). Из терминала примерно:
```
cd "/Users/tolik/Documents/SP_Projects/ASM/GIT/Estex DSS"
sjasmplus --syntax=afw --fullpath -Wno-shortblock \
--lst=Build/DSS-MAIN.LST --raw=Build/DSS/SYSTEM.DOS \
--exp=Build/Variables.inc DSS/DSS-MAIN.ASM
```
- **Деплой в MAME**: VS Code task `Copy all files to MAME` → запускает
[RUN/Image.sh](RUN/Image.sh) (zsh, `hdfmonkey`).
- **Запуск MAME**: VS Code task `Run MAME`.
- **Прочитать CP866-файл с правильной кодировкой**:
```
iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | less
```
- **grep по бинарно-определяемым файлам** (CP866 ломает heuristic):
```
grep -anE "WAITKEY|KEYSCAN" DSS/KEYINTER.ASM
```
Флаг `-a` форсит text-режим.
- **Просмотр с табами**:
```
sed -n '519,540p' DSS/KEYINTER.ASM | tr -d '\r' | cat -tv
```
- **Дамп диспетч-таблицы**:
```
sed -n '337,396p' DSS/DSS-MAIN.ASM
```
---
## 10. Стилевые соглашения
- Кодировка CP866 (DOS Russian), переводы строк CRLF. Edit'ы должны их
сохранять.
- Отступы — табы. Комментарии справа от инструкции выровнены табами до
колонки `;`. При вставке новых комментариев-продолжений использовать
4 таба + `;`.
- Рамки секций — `;████...████` (символ █ = U+2588). Не ломать.
- Стиль меток: метки целиком капсом (`KEYSCAN`, `WAITKEY`), вложенные
через `.` (`KEYSCAN.RESCAN`, `K_SETUP.ERROR`, `Cursor_Off.Flag`).
- Self-modifying code маркирован комментариями вроде `;R01`, `;R13` с
ревизионными номерами — НЕ удалять без причины (это история).
- Хроноразметка `[ ] DD/MM/YYYY` / `[x]` для done — в комментариях.
---
## 10а. Работа с файлами CP866 + CRLF (практические рецепты)
Это **главный источник тонких багов** при редактировании. Прочитай эту
секцию ДО первой правки.
### 10а.1. Что именно в CP866
- Все `DSS/*.asm`, `DSS/*.ASM`, `DSS/*.inc`, `DSS/*.Z80`.
- Все `Shared_Includes/constants/*.inc` и `Shared_Includes/Docs/*.asm`.
- `DSS/CHANGES.LOG`, `DSS/KNOWN.BUG`, `DSS/DSS_MAP.TXT`.
- `Another/SPRINTER/...`, `Another/Docs/Keyboard/KBD.TXT`.
PDF в `Shared_Includes/Docs/sp2000.pdf` и `Another/Docs/*.pdf` — отдельная
история (текст внутри PDF в своей кодировке, см. `pdftotext`).
UTF-8 в этом проекте бывает только в `*.md` (вроде этого файла), `*.json`
(VSCode-конфиги), `*.sh`, `*.lua` и `.gitmodules`.
### 10а.2. Байтовая карта CP866 (минимум, который надо помнить)
| Байт(ы) | Что |
|---------|-----|
| `#80..#8F` | `А..О` (рус. заглавные, первая половина) |
| `#90..#9F` | `П..Я` (рус. заглавные, вторая половина) |
| `#A0..#AF` | `а..п` (рус. строчные, первая половина) |
| `#B0..#B2` | `░ ▒ ▓` (shading) |
| `#B3..#DA` | псевдографика `│ ─ ┌ ┐ ┘ └ ┤ ├ ┬ ┴ ┼ …` и двойные `║ ═` |
| **`#DB`** | **`█` — full block, из которого строятся рамки `;██..██` в этом проекте** |
| `#DC..#DF` | `▄ ▌ ▐ ▀` |
| `#E0..#EF` | `р..я` (рус. строчные, вторая половина) |
| `#F0, #F1` | `Ё ё` |
| `#FC` | `№` |
| `#FE` | `■` |
Важно:
- **`█` = один байт `#DB` в CP866**, а в UTF-8 это **три байта `E2 96 88`**.
Поэтому строчка `;████████` длиной ~110 символов в CP866 занимает ~110
байт, а в UTF-8 — ~330 байт.
- Если случайно конвертнуть файл в UTF-8 и забыть конвертнуть обратно,
размер файла резко вырастает, выравнивание комментариев плывёт, а
sjasmplus может выдать странные ошибки на строковых литералах.
### 10а.3. Просмотр и поиск
**Просмотр через тулзу Read**: русский текст может отображаться как
заполнители или мусор, потому что Read декодирует как UTF-8. Полагаться
на это для содержания комментариев **нельзя** — но для проверки кода
(меток, инструкций Z80, hex-литералов) подходит.
**Просмотр в человеческой форме** — через Bash + iconv:
```sh
iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | less
iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | sed -n '519,540p'
```
**Просмотр с табами/CRLF**:
```sh
sed -n '519,540p' DSS/KEYINTER.ASM | iconv -f CP866 -t UTF-8 | cat -tev
# ^I = табы, $ = конец строки (CRLF будет показан как ^M$)
```
**grep по ASCII-меткам**`-a` обязательно:
```sh
grep -an "WAITKEY\|KEYSCAN\|K_SETUP" DSS/KEYINTER.ASM
```
Без `-a` grep с какого-то порога считает CP866 бинарным и молча выдаёт
только имя файла.
**grep по русским словам** — через pipe iconv (номера строк сохраняются,
т.к. количество переводов строк не меняется):
```sh
iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | grep -an "клавиш"
```
Альтернатива: сконвертить запрос в CP866 и грепать байтами (но это
крайне неудобно — pipe-метод лучше).
**Полный hex-дамп строки** (для разбора, что именно лежит в файле):
```sh
sed -n '519p' DSS/KEYINTER.ASM | xxd | head
```
### 10а.4. Редактирование — критическое
**Главная проблема Edit-инструмента**: он работает на байтовом уровне.
Если файл в CP866, а `old_string` в твоей tool-call'е содержит русский
текст в UTF-8, то байты НЕ совпадут и Edit упадёт с `String not found`.
Если случайно `new_string` запишется UTF-8'ом в CP866-файл, файл
ломается локально (на конкретной строке).
**Безопасные паттерны**:
1. **ASCII-only анкоры (90% случаев)**. Выбирай `old_string` так, чтобы он
состоял из инструкций Z80, меток, hex-литералов и английских/латинских
фрагментов. Кириллический хвост строки пройдёт «как есть» — Edit не
трогает то, что не в old_string.
```
old_string: " RST ToDSS\t\t\t; "
```
Это сработает, даже если справа после `; ` идёт русский комментарий.
2. **Если кириллица обязана быть в old_string** (например, надо заменить
текст внутри русского комментария) — сначала **сконвертировать
фрагмент в CP866** через Bash:
```sh
echo -n "максимальный номер диска" | iconv -f UTF-8 -t CP866 | xxd
```
получишь байты, и можешь искать/заменять через `sed` или `perl` с
`LC_ALL=C` (байтовый режим), а не через Edit.
3. **Удобный трюк**: убедиться, что `old_string` уникально совпадает с
ASCII-частью комментария или ID:
```
old_string: " LD A,disk_num\t\t\t; "
new_string: " LD H,device_num\t\t; "
```
и оставить хвостовую кириллицу в покое.
4. **Не вставляй русский текст напрямую через Edit**, если файл в CP866.
Если требуется добавить русский комментарий, сделай это через Bash:
```sh
echo -n "; новый комментарий" | iconv -f UTF-8 -t CP866 >> /tmp/append
# затем вставляй байты через sed/perl
```
Или согласись писать новые комментарии на английском/латинице (ASCII)
— это безопаснее, и стиль файла это терпит.
### 10а.5. Сохранение CRLF
- Файлы используют `\r\n` (Windows-стиль), не `\n`.
- При вставке новых строк через Edit убедись, что в `new_string` есть
`\r` перед `\n`. Если Edit-инструмент это съест и поставит только
`\n`, файл получит **смешанные line endings** — sjasmplus съест, но
`git diff` будет показывать всю окрестность как изменённую, и в
Windows-редакторах текст рассыпется.
- **Альтернатива**: после Edit-сессии нормализуй переводы строк:
```sh
# Проверить:
file DSS/KEYINTER.ASM
# Должно быть: "with CRLF line terminators"
# Если стало "with CRLF, LF line terminators" — есть смесь, чинить:
perl -i -pe 's/\r?\n/\r\n/g' DSS/KEYINTER.ASM
```
### 10а.6. Проверка после правки (обязательная)
```sh
file DSS/KEYINTER.ASM
# Ожидается: "Non-ISO extended-ASCII text, with CRLF line terminators"
# НЕ должно быть "UTF-8 Unicode text" или "with LF line terminators"
# Проверка, что рамки █ не превратились в UTF-8 (3 байта):
grep -c $'\xDB\xDB\xDB' DSS/KEYINTER.ASM
# Должно быть положительное число (несколько штук). 0 = рамки сломаны.
# git diff с пониманием бинарного представления:
git diff DSS/KEYINTER.ASM | cat -v | head -50
# Cyrillic должен показываться как M-X M-X последовательности байт
```
### 10а.7. Откат, если что-то сломалось
```sh
# Один файл — мгновенный откат:
git checkout HEAD -- DSS/KEYINTER.ASM
# Если уже закоммитил битый файл — посмотреть предыдущую версию:
git show HEAD~1:DSS/KEYINTER.ASM | iconv -f CP866 -t UTF-8 | less
```
### 10а.8. Конвертация всего файла «туда-обратно» (последнее средство)
Если правок очень много и проще править в UTF-8:
```sh
# 1. Конвертация в UTF-8 + переход на LF (так удобнее редактировать):
iconv -f CP866 -t UTF-8 DSS/KEYINTER.ASM | tr -d '\r' > /tmp/KEYINTER.utf8
# 2. Правки в /tmp/KEYINTER.utf8 любым инструментом (Edit/sed/perl).
# 3. Обратно в CP866 + восстановление CRLF:
iconv -f UTF-8 -t CP866 /tmp/KEYINTER.utf8 | perl -pe 's/\r?\n/\r\n/g' > DSS/KEYINTER.ASM
# 4. Проверка:
file DSS/KEYINTER.ASM
git diff --stat DSS/KEYINTER.ASM
```
**Риск**: если в файле есть байты, не имеющие пары CP866↔UTF-8 (теоретически
вряд ли в этом проекте, но возможно при кривых правках в истории), iconv
ругнётся с `illegal input sequence`. Тогда `iconv -c` пропустит проблемные
байты — но они **потеряются**. Безопаснее в этом случае работать
ASCII-only анкорами через Edit.
### 10а.9. VS Code как замена тулинга
Если хочется визуальной правки:
- VS Code: правый-нижний угол → `Reopen with Encoding → Cyrillic (CP866)`.
- Для постоянной ассоциации `.asm/.ASM/.inc` с CP866 добавить в
`.vscode/settings.json`:
```json
{
"[asm]": { "files.encoding": "cp866" },
"files.eol": "\r\n"
}
```
- При сохранении проверь нижнюю панель — индикатор должен показывать
«CP866» и «CRLF», а не «UTF-8» / «LF».
### 10а.10. Чек-лист: «изменения сохранят кодировку»
- [ ] `file <изменённый.asm>``Non-ISO extended-ASCII text, with CRLF line terminators`
- [ ] `grep -c $'\xDB\xDB\xDB' <изменённый.asm>` > 0 (если в файле были рамки)
- [ ] `git diff <изменённый.asm> | wc -l` разумного размера (не в 3× больше ожидаемого — иначе UTF-8 раздувание)
- [ ] Сборка `Build SYSTEM.DOS` проходит без warning'ов вроде «invalid character in string literal»
- [ ] Открыть результат в VS Code с CP866 — комментарии читаются по-русски
---
## 11. Что важно помнить, прежде чем менять KEYINTER
1. **Файл подключается `INCLUDE`'ом** в `DSS-MAIN.ASM:402`, сборка идёт
как монолит. Все символы видны глобально.
2. **Состояние клавиатуры — RAM-only**: не персистится в CMOS. Любая
настройка (язык, локи, sound flags, ASCII-таблицы) теряется на
reset/power-off, если её не сохранить через AUTOEXEC.BAT.
3. **Клавиатура и мышь делят окно INT 38h** — мышь идёт после `KEYSCAN`.
Долгая работа в `KEYSCAN` задерживает мышь.
4. **SIO Ch_A FIFO = 3 байта**`.RESCAN` должен дренировать его
полностью за один заход (текущая реализация это делает через
`JP .RESCAN` на строке 628).
5. **`KEYBOARD_INT_ENABLED=0`** — текущий билд поллит, не использует SIO
INT. Если хочется перейти на прерывания, нужно переключить флаг,
убедиться что `KEYBOARD_INIT` пишет WR1=`#19`, и проверить что
обработчик RFI в `DSS-MAIN.ASM` корректно настроен (Z80 SIO Ch_A only).
6. **При правке HEAD/HOST убедиться, что `RES 6,(HL)` сохранён** — это
обеспечивает wrap modulo 64. Если изменишь размер `SBUF`, поменяй и
маску (бит-логика `RES 6` работает только для 64 = #40).
7. **При правке `XLAT_T`** — длина 144 байта (`Size`), bounds-check
`CP XLAT_T.Size` на строке 860. Если расширяешь, проверь, что не
натыкаешься на следующую область (`CURRENT DIR NAME BUFFER` по
DSS_MAP.TXT идёт от #1343).
8. **Двойственность `D` в записях буфера**: D[7] = «keystroke flag»,
D[6:0] = позиционный код 0..#5A. `INPCODE/CONVERT` ставит бит 7 через
`SET 7,D` — это видимый паттерн.
9. **При правке ASCII-таблиц** — каждая ровно 90 байт, проверяется
`ASSERT .Size = Size`. Если меняешь размер, меняй и `Size EQU 90`
на строке 1286.
10. **Звук работает в обход BIOS** — прямой `OUT (SP_SND.Beeper),A`
(порт #FE бит 4). Это запрещает звуковые эффекты, если процесс
держит порт #FE для других целей (ZX-режим). Учитывать при
модификациях `BEEP`/`FULL_BF`/`LANG_X`.
---
## 12. Чек-лист первого подхода
Сначала открой и прочитай (именно в таком порядке):
1. [DSS/KEYINTER.ASM](DSS/KEYINTER.ASM) целиком (1371 строка).
2. [DSS/DSS-MAIN.ASM](DSS/DSS-MAIN.ASM): строки 187-213 (`RST_10`),
275-310 (`INTx38_Handler`), 337-396 (`DSS_API_TABLE`), 402 (INCLUDE),
423 (`BANKTBL`), 567-600 (boot-флоу).
3. [DSS/defines.inc](DSS/defines.inc) (короткий).
4. [Shared_Includes/Docs/DSS_1.71_Functions.asm](Shared_Includes/Docs/DSS_1.71_Functions.asm), раздел 6.
5. [Shared_Includes/constants/dss_equ.inc](Shared_Includes/constants/dss_equ.inc), приложение А (позиционные коды).
6. `Another/Docs/Sprinter_Programming.pdf` p.22, p.25-26 (страницы 25-26 содержат
пример K_SETUP layout-table).
7. `sp2000.pdf` §9.4 (PDF, страница ~25-26).
8. Скачать Zilog Z80 SIO Technical Manual (UM008), если будешь трогать
`KEYBOARD_INIT` или `KBD_Receiver_Overrun`.
Только после этого формулируй гипотезу и план правок.
---
## 13. Контрольные вопросы перед коммитом
- [ ] Тестовая сборка `Build SYSTEM.DOS` прошла без ошибок?
- [ ] Кодировка файлов сохранена CP866/CRLF? (`file DSS/KEYINTER.ASM`
должен показать `Non-ISO extended-ASCII text, with CRLF line terminators`)
- [ ] Табы и выравнивание комментариев не нарушены?
- [ ] Если изменён публичный API (#30..#37) — обновлён ли
[Shared_Includes/Docs/DSS_1.71_Functions.asm](Shared_Includes/Docs/DSS_1.71_Functions.asm)?
- [ ] Если изменены константы (`Dss.K_SETUP.*`, ASCII-таблица size,
коды ошибок) — обновлён ли `dss_equ.inc`?
- [ ] Если изменена инициализация SIO — проверено, что коммит совместим
с `e334545 "RFI is CH-A ONLY"`?
- [ ] MAME-прогон (`Run MAME` / `Copy all files to MAME`): клавиатура
реагирует, переключение языка работает, INS/CAPS/NUM/SCRL ведут себя
ожидаемо?
- [ ] Echo (`ECHOKEY`) рисует курсор корректно во всех текстовых режимах
(40×32, 80×32)?
- [ ] `XLAT_T` не вылез за `Size=144`?
Удачи.