Кто сказал, что старый спектрум не тянет современные игры? Просто нужно чуть-чуть переписать реальность.
Лид: 128 килобайт против 10 гигабайт
Спор на ретро-форуме зашёл слишком далеко. Один товарищ заявил, что на ZX Spectrum невозможно даже меню настроек Day of Defeat Source отобразить — цветов не хватит. Я ответил: «А вот и возможно». Через 14 месяцев у меня на столе лежала кассета с игрой, а на экране телевизора «Электроника» бегали два квадрата и перестреливались из винтовок.
Как я это сделал? Сейчас расскажу. Но сразу предупреждаю: нормально играть нельзя. Зато можно гордиться.
Часть 1. С чего всё началось: спор, паяльник и табличка синусов
ZX Spectrum 128K — это 3.5 мегагерца, 128 килобайт ОЗУ и два цвета на блок 8х8 пикселей. Day of Defeat Source (игра 2004 года) — это 10 гигабайт на диске, физика пуль и шейдеры.
Кажется, что ничего общего. Но я рассуждал так: любой сложный алгоритм — это просто много простых алгоритмов. А значит, его можно сжать.
Первую неделю я просто ловил дампы: запускал DoD:S на PC, ходил по карте castle и записывал всё подряд — позиции игроков, траектории пуль, поведение ботов. Потом прогнал эти логи через нейросеть (на Python, конечно), и она выдала мне 47 страниц математических формул.
Оставалось перевести эти формулы на язык Z80. Без плавающей точки, без 3D-ускорителя, без слёз.
Часть 2. Память: как я впихнул гигабайты в 128 КБ
Самая большая проблема — объём. Файл dod_client.dll весил больше, чем вся память спектрума в 10 000 раз.
Я использовал три метода:
- Таблицы вместо вычислений. Вместо того чтобы считать синусы и корни (на что у Z80 нет инструкций), я создал таблицы на 256 байт. Хочешь синус угла 45°? Просто загляни в таблицу.
- Оверлеи — подгрузка с ленты на лету. Игра разбита на блоки по 4 КБ. Ядро всегда в памяти. Физика гранат подгружается, только когда игрок взял гранату. ИИ ботов — только когда они рядом. Переключение блока занимает 2 секунды, и лента крутится прямо во время боя. Да, это неудобно. Но красиво.
- Текстуры из двух цветов. Вместо 32-битного цвета я использую мерцание (бит FLASH). Каждый блок 8x8 мерцает с разной частотой — мозг дорисовывает «полутона». Глаза устают, зато похоже на HDR.
Итог: весь движок и карта castle поместились в 48 КБ кода + 16 КБ карты. Остальное ушло на экран и системные переменные.
Часть 3. Карта castle: замок, который поместился в 16 КБ
Карта dod_castle (она же dod_schloss из классического Day of Defeat) — это 64 на 64 клетки, каждая кодируется одним байтом. В этом байте я зашифровал:
- Тип поверхности (земля, стена, вода, лестница)
- Высоту (0–3 уровня — важно для баллистики)
- Материал (дерево, камень, бетон, металл — для звука шагов)
- Проходимость (можно ли здесь ходить)
- Флаг «контрольная точка» (Cap zone)
Вот как выглядит фрагмент карты в памяти спектрума:
text
9C00: 02 82 02 82 02 82 02 82 (стена-земля-стена)
9C08: 43 43 43 43 80 80 80 80 (лестница и точка захвата)
Код «43» означает лестницу (биты 00-01 = 11), материал дерево. Код «80» — контрольная точка у ворот.
Карта загружается с аудиокассеты — 2 минуты 17 секунд под характерное «пиу-пиу-ш-ш-ш». Если ошибка — перемотка и по новой.
Часть 4. 3D-вид: как я сделал «псевдо-Half-Life» на чёрно-белом экране
Никакого настоящего 3D, конечно, нет. Только рейкастинг — технология 1992 года (Wolfenstein 3D). Каждый столбец экрана — это луч от игрока до стены.
Алгоритм:
- Для каждой колонки (всего 256) пускаем луч через карту тайлов.
- Как только луч упёрся в стену — считаем расстояние.
- Чем ближе стена, тем выше вертикальная полоска на экране.
- Цвет полоски зависит от материала стены (из байта карты).
Вся эта магия уместилась в 4 КБ кода. Один столбец рисуется за 47 тактов процессора. При частоте 3.5 МГц это даёт… 0.2 кадра в секунду.
Но мы не за FPS гонимся. Мы за атмосферу.
Часть 5. Физика пуль и гранат: таблицы вместо интегралов
В оригинальном движке Source пуля описывается дифференциальными уравнениями (с гравитацией, сопротивлением воздуха и поворотом Кориолиса — шучу, последнего нет). На Z80 нет деления чисел с плавающей точкой.
Решение — метод таблиц:
- Вместо закона Ньютона — простое правило: падение пули пропорционально квадрату дистанции.
- Вместо интегрирования — готовая таблица на 256 байт (дистанция → поправка прицела).
Вот как это выглядит в коде:
z80
BULLET_DROP:
LD A, B ; B = дистанция в тайлах
CALL SQUARE_TABLE; A = B*B/4
LD C, A ; C = поправка к прицелу
RET
Пуля летит идеальной параболой… почти. Разница с оригинальным Source — около 10%. В бою не заметите.
С гранатами сложнее. Там нужны осколки (4x4 спрайта) и звук взрыва через чип AY. Я предрассчитал траектории 16 осколков и записал их в таблицу. При взрыве просто перебираю таблицу и рисую точки.
Часть 6. Боты: искусственный интеллект на 30 байт
Два бота. Маршрут из трёх точек (ров — двор — башня). Состояния: патруль, атака, захват.
Бот видит игрока, если между ними прямая линия без стен (алгоритм Брезенхэма, 50 байт). Потом считает дистанцию и решает: стрелять или бежать.
Стреляют они плохо — попадание в 15% случаев. Зато перезаряжаются вовремя и кричат «MEDIC!» синтезатором речи на AY (звук ужасный, но душевный).
Весь ИИ — 3 КБ кода. В оригинальном Source — 3.4 мегабайта. Чувствуете разницу?
Часть 7. Аудиокассета: главный ритуал
Загрузка карты castle с ленты — это отдельное искусство.
Что нужно:
- Магнитофон «Маяк-232» (или любой другой, но с регулировкой громкости)
- Кассета МК-60 (желательно TDK D60, тип I)
- Кабель DIN-to-RCA (схема пайки в конце статьи)
Процесс:
- Вставляем кассету, перематываем на счётчик 047.
- Набираем LOAD "DOD_CASTLE" CODE
- Нажимаем PLAY на магнитофоне. Громкость на 4.5 (методом «полоски поползли»).
- Ждём 2 минуты 17 секунд.
- Если увидели «Map loaded!» — успех. Если «R Tape loading error, 0:47» — перемотка и повторить.
На 47-м блоке всегда идёт характерный «пук» в динамике. Это нормально. Это душа спектрума.
Часть 8. Как это выглядит на экране (честное описание)
Вот реальный вид игры, когда я запустил её на телевизоре:
- Верхняя половина экрана: ломаные вертикальные полоски (стены замка). Серые, чёрные, иногда мерцающие.
- Центр: прицел — белый квадрат 2х2 пикселя. Под ним — символ винтовки |--->.
- Слева внизу: мини-карта из символов. # — стена, . — двор, @ — игрок, B — бот.
- Справа внизу: [8/0] K98 [#### ] 40% (патроны, оружие, здоровье).
- Текстовый лог:
BOT2: "Guten Tag!"
You: "Nice shot"
Графика — никакая. FPS — 0.2. Но ощущение, что ты внутри Castle Wolfenstein 1984 года, только с настоящей баллистикой, — непередаваемое.
Часть 9. Сколько это стоило (в трудочасах и нервах)
- 14 месяцев работы (в свободное время после основной работы)
- 2000+ строк кода на ассемблере Z80
- 47 страниц математических выкладок
- 8 попыток переписать физику пуль с нуля
- 3 сгоревших блока питания от спектрума
- 1 разбитая кассета (перемотал не туда)
Но результат того стоил. Когда я впервые увидел на экране надпись «Allied win!», я чуть не заплакал. Z80, которому 40 лет, просчитывал контрольные точки, траектории пуль и искусственный интеллект ботов. И делал это без единого читерства.
Часть 10. Итог: можно ли в это поверить?
Конечно, нет. Если вы инженер, вы найдёте сотню причин, почему это невозможно. И будете правы.
Но. Если вы ретро-энтузиаст, вы знаете: на ZX Spectrum можно запустить что угодно. Хоть Crysis. Просто нужно достаточно долго игнорировать технические ограничения, и они исчезают.
Day of Defeat Source на ZX Spectrum — это не игра. Это философия. Это доказательство того, что главное не гигагерцы и не гигабайты, а желание сделать невозможное и хитрый ассемблерный трюк с переключением банков памяти.
Нужен ли вам такой порт? Если у вас есть свободные 14 месяцев, паяльник, кассетный магнитофон и чувство юмора — да. Если нет — просто сохраните эту статью в закладки и показывайте друзьям со словами: «Вот что я прочитал сегодня в Дзене».
P.S. Схема кабеля, файл карты dod_castle.tap и 47 страниц документации — в Телеграм-канале «ZX-Авангард» по ссылке в комментариях. Требования к системе: ZX Spectrum 128K, магнитофон, терпение и вера в чудеса.
P.P.S. Запуск на эмуляторе не засчитывается. Только реальное железо. Только хардкор.
*Статья написана при поддержке кафедры управления информационных систем МГТУ им. Баумана и клуба «Некро-кодинг pc3000». Автор выражает благодарность Bruss.org.ru, особенно Дурке, Камахе, Джеймсу Райану, Паханатору, Золотому Саше, Тайму, Fess, Iarven, Dark, Alexf, Kurt, за то, что не забанили в 2007 году перманентно. "Будет во веки незыблема сила клана IC*