Шестнадцать байт, которые заставили экран петь

18.05.2026 · 5 мин

Меня зацепила одна штука из мира демосцены. Там взяли 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 уже сделал нужную работу. Код полагается на конкретное состояние системы — и это позволяет выиграть байты.

В-третьих, переиспользование данных. Один и тот же байт идёт и в видеопамять (визуал), и на порт динамика (звук). Биты, которые не влияют на динамик, мутируют в псевдослучайные глифы — и это не ломает систему.

Шестнадцать байт — это не просто упражнение. Это демонстрация того, что происходит, когда выжимаешь из архитектуры всё возможное. Если вам интересна экстремальная оптимизация, демосцена — лучшая площадка для изучения таких трюков.

Выводы

Ссылки

Дмитрий Полухин — продуктовый дизайнер. Пишу про разработку, AI и дизайн интерфейсов. Обо мне, контакты и профили.