docs: справочники по железу Sprinter + правка gfx_scroll_h

- docs/new/ — сводные справочники (архитектура, BIOS, DSS, память,
  графика, акселератор, IRQ, порты, ввод, звук, известные баги);
- docs/Original/ — первоисточники, из которых они собраны (BIOS, Estex
  DSS, мануалы, описание акселератора), + Форум.doc/.docx в reference;
- libbgi/common/gfx_scroll_h.c — обход бага скролла при ширине >256
  (правка автора: шаг банды 255 и продвижение указателей на cw; старый
  вариант с 256 оставлен закомментированным с TODO);
- удалён applications/PoP/roomtest/hang_variants.png — рабочая раскладка
  из разбора позы виса, в репозитории ей не место.

Большие архивы (docs/extra ~568 МБ, docs/sources с вложенными git-репо
~68 МБ) в коммит НЕ включены — см. обсуждение.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 18:26:31 +03:00
parent 1214785a56
commit 64ce6339eb
32 changed files with 23762 additions and 3 deletions
+387
View File
@@ -0,0 +1,387 @@
# 4. Управление памятью Sprinter Sp2000
## 4.1 Аппаратная модель памяти
Z80 видит 64 КБ, разбитые на **четыре окна (window) по 16 КБ**. Каждое окно независимо маппится на физическую страницу (page) ОЗУ или ПЗУ через порт-регистр страницы:
| Окно | Адреса | Порт страницы | Назначение |
|------|--------|---------------|------------|
| W0 | `#0000..#3FFF` | `#82` (PAGE0, ROM.SLOT0) | RST-обработчики, BIOS/DSS ядро |
| W1 | `#4000..#7FFF` | `#A2` (SLOT1, PAGE1) | Приложение |
| W2 | `#8000..#BFFF` | `#C2` (SLOT2, PAGE2) | **Стек — обязательно здесь** |
| W3 | `#C000..#FFFF` | `#E2` (SLOT3, PAGE3) | Разделяемые страницы DSS, графика, резидентный код |
- Запись в порт `#82`/`#A2`/`#C2`/`#E2` меняет физическую страницу окна; чтение возвращает текущую.
- Для совместимости со старыми моделями (ZX-Spectrum 128, Scorpion, Pentagon) работают также порты `#7FFD`, `#1FFD`, `#DFFD`, `#EFF7`:
| Порт | Описание |
|------|----------|
| `#7FFD` | Биты 0-2: страница W3; бит 3: выбор ПЗУ (0=DOS, 1=Basic/48) |
| `#1FFD` | Биты 0-2: страница W0; бит 3: дополнительная память; бит 4: турбо |
| `#DFFD` | Страница W1 (Pentagon, когда бит 5 порта `#7FFD`=0) |
| `#EFF7` | Страница W2 (Pentagon) |
> **Страница `#FF`:** если окно не выделено программе (образ меньше 16/32 КБ), DSS маппит его на специальную страницу `#FF`. Чтение даёт `#FF`, запись игнорируется. Это ключевая причина «молчаливой» порчи данных — см. §4.9.
> **Физические страницы:** порты `#82`/`#A2`/`#C2`/`#E2` адресуют физические 16-КБ страницы в адресном пространстве Sprinter. Диапазон страниц зависит от установленной памяти (обычно 0..127 для 2 МБ, 0..255 для 4 МБ). Старшие страницы заняты видео-ОЗУ и ПЗУ.
---
## 4.2 Окно W0 (#0000..#3FFF) — системное
W0 — критическое окно: в нём живут обработчики `RST 08h` (BIOS), `RST 10h` (DSS/ESTEX) и `RST 18h` (BIOS-EXP). Пока в W0 стоит системная страница, вызовы доступны.
**ROM/RAM switching:**
| Действие | Код |
|----------|-----|
| Включить ПЗУ в W0 | `OUT (#7C),A` (SYS_PORT.ROM) |
| Выключить ПЗУ, вернуть ОЗУ | `OUT (#3C),A` (SYS_PORT.RAM) |
При включённом ПЗУ в W0 отображается системная страница BIOS. Для вызова RST-функций W0 должен быть в ПЗУ — BIOS сам переключает его при входе через `RST 08h`.
**Требования к прерываниям:**
Sprinter работает в режиме **IM1** — вектор прерывания фиксирован на `#0038` (всегда в ПЗУ/ОЗУ W0). Обработчик EXP.asm по адресу `#0038`:
1. Переключает W3 на SYS_PAGE.
2. Проверяет флаг `INT_ID` = `#AA`.
3. Если есть пользовательский обработчик — вызывает его (адрес из SYS_PAGE).
4. Восстанавливает W3, `EI`, `RETI`.
Регистр `I` инициализирован в `#3F` (наследие ZX-Spectrum), но при IM1 не используется для диспетчеризации. Если программа переключается в **IM2**`I` и расположение таблицы векторов определяются программой произвольно, стандартного расположения нет.
**DSS и W0:** при вызове `RST 10h` DSS переключает W0 на свою рабочую страницу (COREPAGE = 4), выполняет функцию и восстанавливает W0. Если в W0 до вызова была ОЗУ, DSS восстановит её; если ПЗУ — вернёт ПЗУ.
**Практические рекомендации:**
- Для вызова системных функций держите W0 в ПЗУ (или не заботьтесь — RST 08h и RST 10h его переключат сами).
- При IM1 (режим по умолчанию) прерывание идёт на `#0038` — это ПЗУ, переключение W0 не требуется.
- При IM2 программа сама управляет таблицей векторов — не забывайте про `DI`/`EI` при переключении W0.
**Размещение своей RAM-страницы в W0:**
Если программе нужно дополнительное окно (например, для атласов спрайтов или кода,
не требующего системных вызовов), можно временно маппить свою страницу в W0. Для
безопасной работы страница должна содержать корректную таблицу RST-векторов в
первые `#40` байт:
| Адрес | Инструкция | Назначение |
|-------|-----------|------------|
| `#0000` | `JP boot` или `RST 00h` (обычно не используется) | |
| `#0008` | `JP rst08_stub` | RST 08h — BIOS |
| `#0010` | `JP rst10_stub` | RST 10h — ESTEX/DSS |
| `#0018` | `JP rst18_stub` | RST 18h — BIOS-EXP |
| `#0020..#0030` | `JP _w0_unused_stub` | RST 20h-30h — не используются |
| `#0038` | `JP isr_stub` | IM1 (RST 38h) |
| `#0066` | `RETN` или `JP nmi_stub` | NMI |
Каждый стаб (в этой же странице, выше `#40`) выполняет одну задачу: сохранить
текущий номер страницы W0, переключить W0 обратно на ПЗУ (`OUT (#7C),A`
SYS_PORT.ROM), выполнить настоящий системный вызов, после чего восстановить
страницу программы и вернуться к исходному caller'у. Типовой паттерн для ESTEX
(`RST 10h`):
```asm
rst10_stub:
IN A, (#82) ; сохранить свою страницу W0
PUSH AF
OUT (#7C), A ; включить ПЗУ в W0 (SYS_PORT.ROM)
; теперь в W0 — системная страница, RST 10h будет обработан корректно
RST #10 ; вызвать настоящий ESTEX (DSS сам переключит W0
; на COREPAGE и восстановит ПЗУ на выходе)
POP BC
LD A, B
OUT (#82), A ; вернуть свою страницу в W0
RET ; возврат к исходному caller'у
```
Такой же паттерн для RST 08h и RST 18h. Для IM1 (`RST 38h`) — аналогично, но
прерывание может прийти в любой момент, поэтому критические секции (своя
страница в W0) обязаны быть обёрнуты в `DI`/`EI` (или проверять вложенность
через счётчик), иначе повторное прерывание при уже замапленной странице
попадёт в стаб, а не в EXP.asm.
**Важно:** пока в W0 стоит ваша страница, системные вызовы (`RST 08h`/`10h`/`18h`)
проходят через стабы и работают корректно, но с накладными расходами
(~10+ тактов на каждый вызов). Код на странице не должен вызывать DSS/BIOS
напрямую — только через RST. При этом сам стаб не должен использовать
RST-инструкции, так как они адресуют W0, который в момент вызова стаба
всё ещё содержит вашу страницу, а не ПЗУ — сначала переключите ПЗУ через
`OUT (#7C),A`.
Перед первым маппингом своей страницы в W0 сохраните текущее значение порта
`#82` (`IN A,(#82)` — оно даёт номер системной/ПЗУ-страницы). Эта страница
понадобится стабам для восстановления после вызова во время прерываний,
когда ПЗУ уже может быть в W0, а стаб не успел его переключить.
Подтверждённая реализация: `tests/w0page` (см. `sprite-api-design.md §9в`).
---
## 4.3 Окно W1 (#4000..#7FFF) — приложение
W1 — основное окно для кода и данных приложения. По умолчанию содержит страницу 5 (RAM, инициализируется BIOS).
**Использование BIOS/DSS:**
BIOS и DSS могут **временно** переключать W1 на свою рабочую страницу. Это происходит:
- При файловых операциях DSS: чтении FAT, загрузке секторов — DSS маппит одну из страниц разделяемого пула в W1 (DIRPAGE, FATPAGE, TXTPAGE, DRVPAGE).
- При графических операциях BIOS: `PIC_FN2`/`PIC_FN3` (блоковые копии) могут переключать W1 на страницу видео-ОЗУ.
- **Важно:** перед возвратом из вызова BIOS/DSS **восстанавливает** предыдущую страницу W1. Типовой паттерн в исходниках DSS:
```asm
IN A,(SLOT1) ; сохранить текущую страницу W1
PUSH AF
... работа с разделяемым буфером ...
POP BC
LD C,SLOT1
OUT (C),B ; восстановить W1
```
Для вызывающей программы переключение W1 полностью прозрачно — после возврата из любой функции DSS/BIOS содержимое W1 идентично тому, что было до вызова.
**Практические рекомендации:**
- Размещайте код и данные в W1, рассчитывая, что он остаётся стабильным между вызовами.
- При длительных файловых операциях (последовательный `READ` больших объёмов) W1 может быть затронут только в моменты внутреннего обращения DSS к FAT/буферам, но не во время собственно чтения данных в буфер программы.
- Если нужно гарантированно сохранить данные через вызов DSS — используйте W2, который BIOS/DSS **никогда не трогают**.
---
## 4.4 Окно W2 (#8000..#BFFF) — стек (критично)
W2 — **единственное окно, которое BIOS и DSS гарантированно не трогают**. Поэтому стек ОБЯЗАН находиться здесь:
- **SP ≤ #BFFF** при вызовах DSS (`RST 10h`).
- **SP ≥ #8000** при вызовах BIOS (`RST 08h`).
- Пересечение → стек в `#8000..#BFFF`.
- По умолчанию SP инициализируется в `#BFFE` (значение из EXE-заголовка, §4.7).
**Почему W2 — единственное стабильное окно:**
| Окно | Может быть переключено BIOS/DSS | Почему |
|------|--------------------------------|--------|
| W0 | Да | DSS переключает на COREPAGE при `RST 10h`; BIOS переключает на ПЗУ |
| W1 | Да, временно | DSS маппит разделяемые буферы для FAT/директорий; BIOS при графических операциях — и **восстанавливает** перед возвратом |
| W2 | **Нет** | **Никогда. Стек должен быть доступен всегда.** |
| W3 | Да, временно | DSS маппит разделяемые буферы; BIOS при графике переключает на видео-ОЗУ; оба **восстанавливают** исходную страницу перед возвратом |
**EXEC и W2:** при загрузке новой программы (`EXEC`, функция 40h) DSS сохраняет страницу W2 в EXSTACK (вместе с W1 и W3). После завершения программы (`EXIT`, функция 41h) W2 восстанавливается.
**Практические рекомендации:**
- Размещайте стек строго в W2. Стандартное значение `SP = #BFFE` — начало стека с запасом ~14 КБ до `#8000`. Можно уменьшить, если программа неглубокая.
- W1 и W3 при вызовах DSS и BIOS **сохраняются и восстанавливаются** (см. §4.3, §4.5), но W2 — единственное окно, которое **никогда не трогается** ни DSS, ни BIOS.
- Если программе нужно больше памяти, чем даёт W2 (например, для хранения больших массивов), выделяйте блоки через DSS `GETMEM` (функция 3Dh) и мапьте их в W1 или W3.
---
## 4.5 Окно W3 (#C000..#FFFF) — разделяемые страницы
W3 — окно, в которое DSS временно маппит страницы своего разделяемого пула для дисковых буферов и служебных данных (адрес `#C000`):
| Страница | Назначение | Адрес в W3 |
|----------|------------|-------------|
| DIRPAGE (0) | Буфер директории | `#C000` |
| FATPAGE (1) | Кеш FAT | `#C000` |
| TXTPAGE / ENVPAGE (2) | Буфер строки/окружения | `#C000..` (PATH_PNT_ARRAY `#FC80`, ENVTEMP `#FE00`) |
| DRVPAGE (3) | Страница драйвера | `#C000` |
| COREPAGE (4) | Ядро DSS (маппится в W0 при `RST 10h`) | — |
**DSS СОХРАНЯЕТ и ВОССТАНАВЛИВАЕТ W3** после каждой операции. Типовой паттерн в исходниках:
```asm
IN A,(SLOT3) ; сохранить текущую страницу W3
PUSH AF
... работа с разделяемым буфером ...
POP BC
LD C,SLOT3
OUT (C),B ; восстановить W3
```
Это означает, что для вызывающей программы переключение W3 полностью прозрачно — после возврата из любой функции DSS содержимое W3 идентично тому, что было до вызова.
**Какие функции трогают W3:**
Функции, обращающиеся к файловой системе, временно маппят разделяемые страницы в W3: поиск файла (`F_FIRST`/`F_NEXT`), чтение FAT (`OPEN`, `READ`, `WRITE`), работа с каталогами (`CHDIR`, `MKDIR`), буферы драйвера диска. Функции без дисковой активности (`VERSION`, `CURDISK`, `WAITKEY`, `LOCATE`, `SYSTIME`, `ENVIRON` и т.п.) W3 не трогают вообще.
BIOS при графических операциях также временно переключает W3 на страницы видео-ОЗУ (например, `PIC_SET_PAL`, `PIC_FN2`) — и тоже восстанавливает.
**Практические рекомендации:**
- W3 можно использовать для данных программы — после возврата из любой функции DSS содержимое W3 не меняется.
- Единственное исключение — `EXEC` (функция 40h): она загружает новую программу, и W3 получает страницы загруженного образа. Это ожидаемое поведение.
- Если программа не вызывает дисковых и графических функций — W3 полностью стабилен.
- **Стек нельзя размещать в W3** — не потому что W3 «теряется», а потому что переключение W3 происходит ВО ВРЕМЯ вызова. Если `SP` указывает на W3, то в момент, когда DSS временно маппит свой буфер, все push/pop внутри DSS будут обращаться к буферной странице, а не к данным программы. Кроме того, при прерывании IM1 обработчик EXP.asm сам переключает W3 на SYS_PAGE — что также сломает стек, если он в W3.
---
## 4.6 Физическая память и страницы
Физическая память Sprinter — не сплошной массив RAM. Разные области адресного
пространства зарезервированы под видео-ОЗУ, ПЗУ и кеш. Номер страницы — это
значение, которое записывается в порт окна (`#82`/`#A2`/`#C2`/`#E2`);
8-битный порт даёт **256 значений** (`#00..#FF`).
**Фиксированная карта страниц:**
| Диапазон | Страниц | Назначение |
|----------|---------|------------|
| `#00..#4F` (0..79) | 80 (1,25 МБ) | RAM общего назначения |
| `#50..#5F` (80..95) | **16 (256 КБ)** | **Видео-ОЗУ (VRAM) — фиксировано в железе** |
| `#60..#DF` (96..223) | 128 (2 МБ) | RAM общего назначения (верхний диапазон) |
| `#E0..#EF` (224..239) | **16 (256 КБ)** | **ПЗУ: BIOS + EXP + ZX ROMs + bitstream — фиксировано** |
| `#F0..#FF` (240..255) | 16 (256 КБ) | **Fast RAM (кеш) / системные страницы** |
**Выделенные страницы:**
| Страница | Назначение |
|----------|------------|
| `4` | **COREPAGE** — ядро DSS (маппится в W0 при `RST 10h`) |
| `5` | Стандартная страница приложения W1 (по умолчанию) |
| `#41` (65) | **Spec_Page** — сохранение состояния при soft-reset [EXP.asm:859,1019] |
| `#FE` (254) | **SYS_PAGE** — системные данные BIOS (INT_ID, INT_ADRESS, буферы) [MAIN.asm:58] |
| `#FF` (255) | **Dummy-страница**: чтение → `#FF`, запись игнорируется |
**Ключевые моменты:**
- **VRAM (`#50..#5F`) фиксирована аппаратно**, независимо от объёма RAM. Эти
16 страниц не являются частью RAM SIMM — это отдельная микросхема 256 КБ.
Биты 2-3 номера страницы кодируют режим записи: `#50` normal, `#54` без
ОЗУ-тени, `#58` прозрачный (байт `#FF` не пишется), `#5C` оба режима.
- **ПЗУ (`#E0..#EF`)** — 256 КБ флеш-памяти с BIOS, EXP, ZX Spectrum 48/128 ROM,
TR-DOS, логотипом и битстримом для CPLD.
- **Fast RAM (`#F0..#FF`)** — до 512 КБ быстрого SRAM (0 wait-states),
доступна через порт `#FB`. `#FE` (SYS_PAGE) и `#FF` (dummy) — часть этого
диапазона. При включённом кеше порт `#FB` конфликтует с CBL-звуком.
- Для 2 МБ RAM: `#60..` заполнены до `#7F` (32 стр. = 512 КБ); остаток
`#80..#DF` — неиспользуемые страницы (отображаются как `#FF`).
- Для 4 МБ RAM: `#60..#DF` целиком заполнены RAM (128 стр. = 2 МБ).
> **Нумерация:** порты `#82`/`#A2`/`#C2`/`#E2` дают прямой доступ по
> физическому номеру (#00..#FF). Порты `#7FFD`/`#1FFD` (совместимость с
> ZX-Spectrum 128) используют сквозную нумерацию: номер в `#7FFD` (0..7)
> маппится на физические страницы видеоОЗУ `#50..#5F` младшими 3 битами;
> для работы с произвольными страницами используйте порты `#82`/`#A2`/`#C2`/`#E2`.
---
## 4.7 EXE-файл: формат и загрузка
Загрузка исполняемых файлов выполняется функцией DSS `EXEC` (40h). Формат EXE-файла:
**Заголовок (512 байт):**
| Смещение | Размер | Поле | Описание |
|----------|--------|------|----------|
| +0 | 3 | `EXE_EXT` | Сигнатура `"EXE"` |
| +3 | 1 | `VERSION` | Минимальная версия DSS (1 = v1.xx, 0 = без пути/аргументов) |
| +4 | 2 | `OFFCOD1` | Младшее слово смещения образа в файле |
| +6 | 2 | `OFFCOD2` | Старшее слово смещения (OFFCOD2 << 16 + OFFCOD1 = offset) |
| +8 | 2 | `LOADER` | Размер первичного загрузчика (0 = весь образ сразу) |
| +10 | 6 | `RESERVED` | Зарезервировано |
| +16 | 2 | `LD_ADDR` | Адрес загрузки (`#4100..#FFFF`, старший байт маскирован `#3F`) |
| +18 | 2 | `PC_REG` | Точка входа (entry point) |
| +20 | 2 | `SP_REG` | Начальный SP (по умолчанию `#BFFE`) |
| +22 | 1 | `UnUsedPoint` | Не используется |
| +23 | 489 | `RESERVED2` | Зарезервировано (обнуляется; используется как временный стек EXEC) |
**Процесс загрузки (EXEC, функция 40h):**
1. Открыть файл на чтение.
2. Считать 512-байтный заголовок в буфер ядра.
3. Проверить сигнатуру `"EXE"` и версию (≥ минимальной 1).
4. Если `LOADER=0`: seek на смещение `OFFCOD2:OFFCOD1`, считать весь образ по адресу `LD_ADDR`.
5. Если `LOADER>0`: считать только первичный загрузчик по адресу `LD_ADDR` (он дочитает остальные сегменты).
6. Выделить блок памяти через `GETMEM` (функция 3Dh), зарегистрировать в таблице RAM.
7. Сохранить текущие страницы окон (SLOT1-3) в EXSTACK.
8. Определить, сколько страниц нужно, и замаппить их через `SETWIN1`/`SETWIN2`/`SETWIN3`:
- Если `LD_ADDR ≥ #C000`: маппится только W3.
- Если `LD_ADDR ≥ #8000`: маппятся W2 и W3.
- Иначе: маппятся W1, W2 и W3.
9. Построить **PSP** (Program Segment Prefix) — блок данных сразу ниже `LD_ADDR`:
- `LD_ADDR-3`: файловый манипулятор (file handle).
- `LD_ADDR-2`: идентификатор блока памяти.
- `LD_ADDR-1`: номер задачи.
- `LD_ADDR+0`: размер CLP (командной строки).
- `LD_ADDR+1..+128`: CLP-буфер.
- После CLP: строка пути `"A:\DIR\FILE.EXE",0`.
10. Установить SP из заголовка (`SP_REG`), сохранить IX → адрес PSP.
11. Если `VERSION=0` — установить текущий каталог из пути PSP.
12. Затолкать в стек адрес возврата (RETFAR к DSS), **EI**, JP на точку входа.
**Multi-bank EXE:**
При наличии нескольких банков (`LOADER>0`) файл устроен так:
```
[512-байт заголовок] [HOME-сегмент] [Банк 1 (16 КБ)] [Банк 2 (16 КБ)] ...
```
- HOME-сегмент: образ, загружаемый DSS по `LD_ADDR`.
- Банки: 16-КБ блоки, каждый на своей физической странице.
- `LOADER` = размер HOME-сегмента (DSS загружает только его; первичный загрузчик сам дочитывает банки через `READ` из оставшегося открытым файла).
---
## 4.8 Прерывания и память
**Обработчик IM1 (`RST 38h`):**
Вектор фиксирован на `#0038` (W0). Реализация в EXP.asm:
1. Переключает W3 на SYS_PAGE (BIOS).
2. Проверяет флаг `INT_ID` = `#AA` (установлен при инициализации BIOS).
3. Если флаг совпадает — вызывает пользовательский обработчик, адрес которого хранится в SYS_PAGE (`INT_ADDRESS`, `INT_PAGE`).
4. После возврата восстанавливает W3 и выполняет `EI; RETI`.
Это позволяет пользовательским программам устанавливать свои обработчики INT без полного перехвата вектора.
**Обработчик IM1 (системный):**
При возникновении прерывания Z80 исполняет `RST 38h` → вызов по адресу `#0038`. Реализация в EXP.asm (всегда доступна, т.к. `#0038` в W0):
1. Переключает W3 на SYS_PAGE (BIOS).
2. Проверяет флаг `INT_ID` = `#AA` (установлен при инициализации BIOS).
3. Если флаг совпадает — вызывает пользовательский обработчик, адрес и страница которого хранятся в SYS_PAGE (`INT_ADDRESS`, `INT_PAGE`). Это позволяет программам устанавливать свои ISR без полного перехвата вектора.
4. После возврата восстанавливает W3 и выполняет `EI; RETI`.
**Обработчик IM2 (опционально, для продвинутых программ):**
Sprinter по умолчанию не использует IM2. Исходники содержат шаблон `IM2_INT.asm` для программ, желающих переключиться на IM2:
1. Сохраняет AF, BC, DE, HL, IX, IY и теневые регистры.
2. Включает ПЗУ (`OUT (#7C),A`) — KEYSCAN требует ПЗУ в W0.
3. Вызывает KEYSCAN (опрос AT-клавиатуры).
4. Восстанавливает все регистры, `EI`, `RETI`.
При переходе на IM2 программа сама выбирает значение `I` и размещает таблицу векторов в соответствующем диапазоне памяти.
**Рекомендации:**
- Если программа не использует прерывания — держите W0 в ПЗУ.
- Если программа использует IM1 — можно переопределить обработчик через структуру в SYS_PAGE (флаг `INT_ID`, поля `INT_ADDRESS`/`INT_PAGE`).
- Если программа использует IM2 — переключение полностью на ответственности программы; ядро Sprinter не предоставляет стандартного расположения таблицы векторов.
---
## 4.9 Типовые грабли
- **Данные читаются как `#FF` / не сохраняются** — код или данные попали в незамапленное окно (страница `#FF`). Причина: образ программы меньше 16 КБ, DSS выделил только одну страницу, а код/данные распределены линкером в незамапленные окна. Лечение: убедитесь, что код и данные укладываются в одно окно, или явно выделяйте и мапьте остальные окна через `GETMEM`/`SETWIN1`-`SETWIN3`.
- **Крэш при вызове RST при нестандартном W0** — если перед вызовом W0 содержал произвольную страницу ОЗУ, DSS при `RST 10h` переключит его, но может не восстановить корректно. Решение: перед вызовом RST включите ПЗУ (`OUT (#7C),A`), если не уверены в состоянии W0.
- **Стек в W3 (не-рабочий вариант)** — если стек размещён в W3, при любом дисковом вызове W3 будет переключён на разделяемый буфер DSS, и стековые данные будут забиты. Результат: мгновенное зависание при возврате из функции. Единственное рабочее место для стека — W2.
- **CBL не работает при включённом кеше** — порт `#FB` используется как кеш-память (Fast RAM) и как порт CBL-звука. Если кеш включён, запись в `#FB` уходит в кеш, а не в CBL. Детали: `11-ports.md`, `12-bugs.md`.
- **Прерывание уходит не туда при IM2** — если программа переключилась в IM2 с неинициализированной таблицей векторов, любое прерывание приведёт к исполнению мусора. Решение: используйте IM1 (режим по умолчанию) или инициализируйте таблицу перед переключением.
- **Не путать W3 с «теряется после вызова»** — многие источники утверждают, что DSS сбрасывает W3. На самом деле DSS **сохраняет и восстанавливает** W3 после каждой операции. Стек всё равно нельзя размещать в W3 (см. §4.5), но для данных W3 полностью стабилен между вызовами. Если ваша программа полагается на старый миф — проверьте код.
---
## 4.10 Кросс-ссылки
- Порты страниц и детали регистров: `11-ports.md`
- BIOS (графика, окна): `02-bios.md`
- DSS/ESTEX (EXEC, GETMEM, SETWIN): `03-dss.md`
- Прерывания: `07-irq.md`
- Баги (CBL vs кеш, порт #FB): `12-bugs.md`
- EXE-формат и PSР: `memory/exe-format.md`