Шестнадцать байт, которые заставили экран петь
Меня зацепила одна штука из мира демосцены. Там взяли 16 байт ассемблерного кода для x86 — и заставили их одновременно рисовать фрактал Sierpinski и генерировать звук. Без внешних библиотек, без операционной системы. Просто голый код, работающий с памятью видеoadаптера и динамиком.
Разбираемся, как это устроено.
Идея: видеопамять как калькулятор
Суть трюка в том, что видеопамять VGA (адрес 0xB800) используется не как хранилище пикселей, а как пространство для вычислений. После вызова прерывания BIOS int 10h (установка текстового режима 40×25) экран очищается, но память не заполняется нулями. Каждый символ на экране — это два байта: ASCII-код символа и байт цвета. BIOS ставит везде 0×20 (пробел) и 0×07 (серый текст на чёрном фоне).
Результат: экран выглядит пустым, но это идеально однородная среда для алгоритма. Если бы память содержала случайный мусор, фрактал бы не получился.
Код: 16 байт, которые всё меняют
int 10h ; 2 байта — устанавливаем видеорежим
mov bh, 0xb8 ; 2 байта — подготавливаем адрес видеопамяти
mov ds, bx ; 2 байта — DS = 0xB800
L:
lodsb ; 1 байт — загружаем байт по адресу SI в AL
sub si, byte 57 ; 3 байта — SI -= 56 (шаг назад)
xor [si], al ; 2 байта — XOR текущего байта с AL
out 61h, al ; 2 байта — отправляем байт на порт динамика
jmp short L ; 2 байта — повторяем
Итого 16 байт. Каждая инструкция выжата до предела.
Механика: почему это работает
Шаг 1: инициализация. int 10h делает две вещи: переключает видеорежим (BIOS сам заполняет память пробелами) и подготавливает регистры. Дальше mov ds, bx устанавливает сегмент данных на начало видеобуфера. Теперь все обращения к памяти идут прямиком в видеокарту.
Шаг 2: чтение и модификация. lodsb загружает байт из памяти в регистр AL. Затем sub si, byte 57 отнимает 56 от указателя. Вместе с автоматическим инкрементом от lodsb это даёт чистый сдвиг на -56 байт за итерацию.
Ключевой момент — xor [si], al. Инструкция читает байт из памяти, XOR-ит с AL и записывает обратно. Это создаёт так называемый prefix sum (накопленную сумму) по модулю 2.
Шаг 3: почему получается фрактал. Если использовать add вместо xor, значения будут накапливаться с переносом битов. Но xor отсекает перенос — остаётся только сложение по модулю 2. Для начального значения 2 (бинарное 00000010) это означает, что алгоритм работает только с Bit 1.
Результат — клеточный автомат Rule 60 по классификации Стивена Вольфрама. Каждый новый бит зависит от предыдущего и текущего: Cell[p][k] = Cell[p-1][k] XOR Cell[p][k-1]. Это и есть фрактал Sierpinski.
Шаг 4: звук из геометрии. out 61h, al отправляет вычисленный байт на порт 61h. Этот порт управляет встроенным динамиком PC. Bit 1 порта напрямую толкает динамик вперёд (1) или назад (0). Поскольку наш алгоритм работает только с Bit 1, фрактал напрямую становится набором инструкций для динамика.
CPU работает на фиксированной частоте, которая и задаёт sample rate. Результат — квадратная волна, чья частота зависит от структуры фрактала. Ряды с чередованием 1/0 дают высокий тон, блоки нулей — паузы.
Диагональный паттерн: магия числа 56
Шаг в -56 байт выбран не случайно. Сегмент DOS — 65536 байт. Шаг 56 не делит его нацело (НОД = 8). Цикл проходит 8192 итерации, прежде чем вернуться в начало. Это вдвое больше стандартных 4096 — частота падает на одну октаву.
Для визуала: шаг назад на 56 байт эквивалентен сдвигу вперёд на 24 байта на строке шириной 80 байт. Это 12 столбцов. НОД (12, 40) = 4, значит алгоритм затрагивает только 10 из 40 столбцов. Фрактал разбивается на 10 диагональных колонн, поднимающихся вверх по экрану.
ПАМЯТЬ И ПОРТЫ В 16 БАЙТАХ
───────────────────────────
Видеопамять (0xB800) Порт динамика (61h)
┌──────────────────┐ ┌──────────────────┐
│ BIOS заполняет │ │ Bit 1 → динамик │
│ 0x20 (пробел) │ │ Bit 0 → управление│
│ 0x07 (цвет) │ └────────┬─────────┘
└────────┬─────────┘ │
│ чтение/запись │ вывод
▼ ▼
┌─────────────────────────────────────────────────┐
│ lodsb → XOR [si], al → out 61h, al │
│ (AL содержит Bit 1 = 0 или 2) │
└─────────────────────────────────────────────────┘
Ограничения: зависимость от памяти
Теоретическая модель исходит из идеально однородной памяти. В реальности разные BIOS, видеокарты и эмуляторы (DOSBox, PCem) оставляют разный мусор в верхних областях RAM. Поскольку алгоритм читает и XOR-ит всё подряд, результат зависит от начального состояния памяти.
Можно добавить код очистки памяти, но это увеличит размер. Для строгого 16-байтного лимита приходится мириться с вариативностью — и превращать это в особенность, а не баг.
ШАГ -56: ДИАГОНАЛЬНАЯ СТРУКТУРА
───────────────────────────────
Экран 40x25 символов (80 байт/строка)
Шаг -56 ≡ +24 (mod 80) = 12 столбцов
НОД(12, 40) = 4 → 10 колонн из 40
Колонка 0 4 8 12 16 20 24 28 32 36
│ │ │ │ │ │ │ │ │
Строка 0 ●────●────●────●────●────●────●────●────●
│ │ │ │ │ │ │ │ │
Строка 1 ●────●────●────●────●────●────●────●────●
│ │ │ │ │ │ │ │ │
Строка 2 ●────●────●────●────●────●────●────●────●
● = посещённая ячейка (фрактал рисуется здесь)
Что это значит для инженера
Здесь несколько уроков. Во-первых, побочные эффекты инструкций — это не помеха, а инструмент. lodsb сам инкрементирует SI, xor [si], al читает и пишет за одну операцию. Каждая инструкция делает две вещи сразу.
Во-вторых, отказ от инициализации. Не нужно обнулять регистры, если BIOS уже сделал нужную работу. Код полагается на конкретное состояние системы — и это позволяет выиграть байты.
В-третьих, переиспользование данных. Один и тот же байт идёт и в видеопамять (визуал), и на порт динамика (звук). Биты, которые не влияют на динамик, мутируют в псевдослучайные глифы — и это не ломает систему.
Шестнадцать байт — это не просто упражнение. Это демонстрация того, что происходит, когда выжимаешь из архитектуры всё возможное. Если вам интересна экстремальная оптимизация, демосцена — лучшая площадка для изучения таких трюков.
Выводы
- 16 байт кода могут одновременно рисовать и звучать.
- Видеопамять можно использовать как вычислительную среду.
- XOR делает из простого обхода памяти фрактал Sierpinski.
- Один и тот же бит способен управлять и картинкой, и звуком.
Ссылки
- Оригинальный write-up на hellmood.111mb.de — подробный разбор трюка с 16 байтами
- Демо на Demozoo — каталог демосценовых работ
- Rule 60 — клеточный автомат Вольфрама — справка о клеточных автоматах и фракталах
Дмитрий Полухин — продуктовый дизайнер. Пишу про разработку, AI и дизайн интерфейсов. Обо мне, контакты и профили.