Как демо Memories умещается в 256 байт

в 7:19, , рубрики: demo, demoscene, intro, MIDI, sizecoding, x86 assembly, ассемблер (x86), Демосцена, Работа с 3D-графикой, разработка игр
Как демо Memories умещается в 256 байт - 1

Введение

Привет! Меня зовут «HellMood», а эта статья посвящена небольшой программе для MS DOS под названием «Memories». Эта программа имеет размер 256 байт, она выиграла в категории «PC 256 byte» соревнований демосцены «Revision» 2020 года, а также получила приз зрительских симпатий. Видео вывода программы можно посмотреть здесь, а видео с реакцией онлайн-аудитории и модераторов — здесь. Скачать релиз и оставить комментарии можно здесь. В этой статье будет представлен глубокий анализ программы, рассказано об исторических отсылках и этапах разработки. Статья выложена в sizecoding wiki. Она не только позволит вам понять внутреннее устройство «Memories», но и поможет самим создать нечто похожее. Изучите её! Если вы новичок в sizecoding-е (написании программ в рамках нужного размера) или в ассемблере x86, то рекомендуется начинать с основ этой wiki. Принципы понять легко, но с подробностями реализации разобраться бывает не так просто.

Краткий обзор

В этой статье мы будем говорить об отправленной на конкурс версии для DosBox (256 байт). В архиве также содержатся версии для FreeDos и Windows XP DOS, которые на момент написания поста работали не на всех компьютерах. Эти альтернативные версии были включены в архив как proof of concept, чтобы показать, что программа не только работает в эмуляторе. В категории «PC 256 bytes» соревнований «Revision» 2020 года можно было указать в качестве платформы «FreeDos» или «DosBox» (последняя в конкретной конфигурации). Как доказывают альтернативные версии, на самом деле можно модифицировать версию для DosBox так, чтобы она работала во FreeDos, MS DOS, WinXP и Win98, но статья будет не об этом.

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

История небольших эффектов

Как демо Memories умещается в 256 байт - 2

Категории размеров на http://www.pouet.net

Мы, сайзкодеры, рассуждаем в категориях размеров. Для MS DOS такими категориями являются 256b, 128b, 64b и 32b. Это стандарты одного из крупнейших архивов демосцены www.pouet.net. Категории 16b нет, однако многие небольшие эффекты можно реализовать в 16 байтах. Почти все эффекты из «Memories» писались и оптимизировались мной ранее, и в основном их реализации стали попытками снизить уже существующий размер эффекта, или создать нечто похожее, но меньшего размера. Под снижением размера здесь подразумевается его уменьшение до одного из следующих меньших категорий 2^N. Например, если эффект был реализован в 33-64 байтах, то его уменьшают до 32 байт или меньше. Почти каждый раз, когда мне удавалось «спустить» эффект в более низкую категорию, я отправлял получившуюся крошечную программу на посвящённое демосцене мероприятие, в котором разрешалось участие удалённых участников в категории 256b, и/или публиковал результат на www.pouet.net. В этом разделе я познакомлю вас с эффектами, а также расскажу об их истории происхождения и авторах.

Array of chessboards

Как демо Memories умещается в 256 байт - 3

kasparov, 16 байт

Источником этого эффекта стал мой собственный «Kasparov 16b» 2018 года (ссылка). Похоже (цитата из примечаний к релизу), я «просто сделал эту доску, уместил её в 17 байт, но это было не очень красиво, пока я не придумал трюк...». Раньше уже был похожий эффект, реализованный в 32 байтах: «ew» 2003 года от разработчика «headcrash». (ссылка) В данном случае я стремился реализовать «настоящие» шахматные доски с полем 8x8 и узнаваемыми чёрными и белыми клетками, а также правильную ориентацию отдельных шахматных досок, то есть нижний правый угол (h1) должен был иметь белый цвет. Чтобы этот эффект работал с глобальным фреймворком, в «Memories» его пришлось реализовать заново при помощи другой техники: записи на экран; кроме того, было изменено направление скроллинга, чтобы он отличался от эффекта «scrolling tilted plane».

Zooming circles

Масштабируемые круги (zooming circles) должны были участвовать в демопати как интро на 32 байт, но я так этого и не сделал. У этого эффекта нет предшественника на 64 байт, потому что в категории 64b возможны гораздо более сложные эффекты. Zooming circles стали результатом моей отчаянной попытки попасть в категорию 32b с эффектом круглого «туннеля», для которого мой личный рекорд по-прежнему 52 байта («Neontube» — 2016 год) (ссылка), который, в свою очередь, стал оптимизацией классического 64-байтного эффекта «constant evolution» разработчиков ryg/Farbrausch (2003 год) (ссылка). В процедуре zooming circles расстояние и угол устранены/игнорируются, благодаря чему удалось попасть в категорию 32b.

Scrolling tilted plane

Как демо Memories умещается в 256 байт - 4

Floorcast, версия на 32 байта, вариация

Scrolling tilted plane — это один из моих собственных релизов, «floorcast 32b» 2018 года. Эффект «напольных покрытий» имеет собственную историю в sizecoding-е и постепенно уменьшился с 256 байт до 32 байт. Разные версии отличаются количеством плоскостей, в некоторых отображется две плоскости, в других — только одна. В релизе «floorcast 32b» я специально решил отказаться от текстуры «XOR», а в «Memories» снова её использовал, замаскировав её финальным штрихом в виде «AND».

  • Версия «rain_storm» 2008 года — 256 байт — ссылка
  • Версия «org_100h» 2008 года — 128 байт — ссылка
  • Версия «Baudsurfer» 2013 года — 86 байт — ссылка
  • Версия «Baudsurfer» 2014 года — 64 байт — ссылка
  • Версия «HellMood» 2018 года — 32 байт — ссылка

Parallax checkerboards

Как демо Memories умещается в 256 байт - 5

Projektbeschreibung, 32 байт

Шахматные доски с параллаксом я выпустил в 2018 году в виде 32-байтного эффекта «Projektbeschreibung». В нём я пытался снизить размер «Follow the light» (ссылка) из «Digimind» (2006 год) или моей собственной «Lucy» (2014 год) (ссылка) до 32 байт. Очень полезными источниками вдохновения послужили «Paralaxa» автора Rrrolas (32 байт, 2007 год, ссылка) и «Byteropolis» автора Sensenstahl (2013 год) (ссылка). Техника рендеринга Rrrolas была очень близка к моему окончательному решению, код был изменён для исправления расположения плоскостей, замены треугольников на шахматные доски и улучшения цветов. В «Memories» использовала цветовая схема версии «Digimind». Кроме того, эффект был максимально модифицирован для снижения деформаций.

Sierpinski rotozoomer

Как демо Memories умещается в 256 байт - 6

colpinski, 16 байт

Как демо Memories умещается в 256 байт - 7

rotastic, 32 байт

Он состоит из двух эффектов: rotozoomer и эффекта Серпинского в качестве текстуры. Эффект Серпинского основан на моём собственном эффекте «Colpinski 16b» 2013 года (ссылка), где максимум возможного удалось получить при помощи «frag» функции «fsqrt». У этого эффекта нет предшественника, потому что его можно реализовать напрямую комбинированием X и Y, а не использовать систему итерируемых функций (Iterated Function System) или клеточные автоматы. «Поворот и масштабирование» (rotozoomer) был выпущен мной в 2017 году как 32b-интро «rotastic» (ссылка), он основан на идеях «ryg» разработчика Farbrausch (51 байт, 2002 год, ссылка) и «Gargaj» группы «Conspiracy» (49 байт, 2002 год, ссылка).

Raycast bent tunnel

Как демо Memories умещается в 256 байт - 8

Into a new era, 64-байтная версия

Изогнутый туннель с рейкастингом (raycast bent tunnel) стал модифицированной версией моего собственного 64-байтного релиза «Into a new era» (2018 год, ссылка). Оригинальные цвета были заменены на цвета стандартной палитры, немного изменена геометрия и соответствующие вычисления, чтобы для вычисления значений текстур не использовался эффект глубины. Отдельная версия этого эффекта имеет размер 50 байт. Основными источниками вдохновения для создания 64-байтной версии стали два 128-байтных интро: «Spongy» от «TBC» (2009 год, ссылка) и «Wolf128» автора Baudsurfer (2014 год, ссылка), а алгоритм я разрабатывал самостоятельно.

Ocean night to day

Как демо Memories умещается в 256 байт - 9

Ocean, версия 64b

Эффект океана основан на моём собственном 64-байтном релизе «Ocean» 2016 года (ссылка). Были вырезаны отдельная генерация цветов и музыки оригинала, оба генератора не были совместимы с основным фреймворком «Memories» без использования КУЧИ лишних байтов. Спецэффект «рассвета» происходит благодаря общей реализации структуры фреймворка. Об этом я расскажу в следующей главе.

Fading effect

Переход между двумя эффектами сам по себе является эффектом, у которого нет предшественников. Скорее, это идея, которая эволюционировала в течение нескольких лет, и возможно, она похожим образом была реализована многими другими. Если вкратце, то при вычислении кадра позиция каждого пикселя рандомизируется, а время, определяющее, какой эффект надо использовать, смещается на это значение рандомизации, которое было предварительно уменьшено. Это позволяет использовать стандартную VGA-палитру (изображение, источник), а не создавать собственные цвета для плавного смешивания, благодаря чему экономится пространство.

Фреймворк моего «крошечного мегадемо»

Чтобы объединить несколько маленьких эффектов в одно «мегадемо», в них должна использоваться одинаковая техника и они по большей мере не должны применять допущения (относительно содержимого памяти и регистров). Также они обязаны использовать одинаковые значения таймингов и работать в соответствии с общим временем. На подготовку отдельных эффектов, подходящих для фреймворка потребовалось довольно много времени, а поначалу — и много дополнительного места. Нужно заметить, что некоторые из самых впечатляющих эффектов (если судить по реакции зрителей и просмотрам в соцсетях) невозможно было включить в демо из-за ОГРОМНОГО избыточного использования памяти. После того, как работа всех эффектов была налажена, я мог начать думать над «вынесением за скобки» часто повторяющихся вычислений, что позволило бы сэкономить ещё немного байтов. Фреймворк выполняет следующие дейсвтия:

  • Переключение в экранный режим 320 x 200 пикселей с 256 цветами
  • Инициализация указателя на экран
  • Установка обратного вызова для отсчёта времени и музыки
  • Основной цикл
    • Вычисление X и Y из текущей точки экрана
    • P = псевдослучайное значение из точки экрана
    • Смещение текущего времени T на отмасштабированную величину P'
    • Выбор номера эффекта N в соответствии с T'
    • Выполнение эффекта N (X,Y) для текущего пикселя
      • На входе XY находятся в регистре DX (DL,DH)
      • На входе текущее время находится в регистре BP
      • На выходе в AL ожидается цвет пикселя
    • Переход к следующему пикселю
    • Тройной диагональный интерлейсинг для сглаживания
    • Повторять до завершения кадра
  • Установить таймер на темп примерно 35 FPS
  • Проверить клавиатуру на нажатие ESC
    • Выйти при нажатии ESC, в противном случае продолжать

Код фреймворка

org 100h
s:
	mov al,0x13				; set AL to mode 320*200 in 256 colors
	int 0x10	 			; call BIOS to set mode
	xchg bp,ax				; set timing value to 0x13 
	push 0xa000-10			; write the screen adress to register ES
	pop es					; works in conjunction with Rrrola trick
	mov ax,0x251c			; parameter for changing timer interrupt
	mov dl,timer			; adress of timer routine, assume DH=1
	int 0x21				; install timer routine
top:
	mov ax,0xcccd			; load magic Rrrola constant
	mul di					; transform screen pointer to X, Y
	add al,ah				; use transformation garbage as
	xor ah,ah				; pseudorandom value and clear AH
	add ax,bp				; add time value to random value
	shr ax,9				; divide by 512 (basically the speed)
	and al,15				; filter effect number
	xchg bx,ax				; move effect number to BX
	mov bh,1				; reset BH to align with start of code
	mov bl,[byte bx+table]	; read the effect address from the table
	call bx					; call the effect
	stosb					; write the return value and advance
	inc di					; triple interlace trick for after
	inc di					; effect and smoothing the animation
	jnz top					; repeat until the frame is complete
	mov al,tempo			; set AL to divider for timer
	out 40h,al				; set timing (dual pass)
	in al,0x60				; read keyboard
	dec al					; quit on ESC
	jnz top					; otherwise repeat loop
sounds: db 0xc3, 11, 0x93; 0xc3 is MIDI/RET; fx2-s is used as volume
table: 	db fx2-s,fx1-s,fx0-s,fx3-s,fx4-s,fx5-s,fx6-s,sounds-s,stop-s

Объяснение эффектов

Array of chessboards

Как демо Memories умещается в 256 байт - 10

array of chessboards

Самый простой эффект, с которого хорошо начинать. После смещения строки на время применяется класссический паттерн XOR. Чтобы создать впечатление сетки из шахматных досок, в цвете задаются все биты, кроме двух. Настоящая хитрость заключается в сдвиге в «хорошее» место палитры. Та часть кадра, которая воспринимается чёрной, на самом деле не чёрная, а является тёмной частью стандартной VGA-палитры. Такой сдвиг позволяет придать тёмным и светлым клеткам ощущение древней шахматной доски.

	xchg dx,ax		; get XY into AX
	sub ax,bp		; subtract time from row
	xor al,ah		; XOR pattern (x xor y)
	or al,0xDB		; pattern for array of boards
	add al,13h		; shift to good palette spot

Zooming circles

Как демо Memories умещается в 256 байт - 11

zooming circles

Расстояние D от точки (X,Y) до центра (0,0) равно sqrt(X²+Y²). Фреймворк предварительно делает так, чтобы DL содержал центрированную координату X, а DH=Y нужно центрировать в коде. Для выполнения операции вычисления квадратного корня в x86 требуется довольно много кода, но на самом деле его можно не использовать. При продуманном выборе цветов ощущение сходящихся кругов без квадратного корня кажется вполне убедительным.

	mov al,dh		; get Y in AL
	sub al,100		; align Y vertically
	imul al			; AL = Y²
	xchg dx,ax		; Y²/256 in DH, X in AL
	imul al			; AL = X²
	add dh,ah		; DH = (X² + Y²)/256
	mov al,dh		; AL = (X² + Y²)/256
	add ax,bp		; offset color by time
	and al,8+16		; select special rings

Scrolling tilted plane

Как демо Memories умещается в 256 байт - 12

scrolling tilted plane

Этот эффект реализован следующим образом: сначала для имитации расстояния большая константа делится на строку номер Y. Затем полученное значение используется дважды: a) умноженное на центрированное значение X и b) как смещённое на текущее время. Затем эти результаты комбинируются при помощи паттерна XOR, из которого выбирается специальный паттерн.

	mov ax,0x1329	; initialize with constant
	add dh,al		; preventing divide overflow
	div dh			; reverse divide AL = C/Y'
	xchg dx,ax		; DL = C/Y', AL = X
	imul dl			; AH = CX/Y'
	sub dx,bp		; DL = C/Y'-T 	
	xor ah,dl		; AH = (CX/Y') ^ (C/Y'-T)
	mov al,ah		; move to AL
	and al,4+8+16	; select special pattern

Parallax checkerboards

Как демо Memories умещается в 256 байт - 13

parallax checker boards

Это своего рода рейкастинг (испускание лучей) с динамической геометрией. Каждая плоскость объектов разделена по горизонтали из-за 16-битного умножения со знаком и по вертикали из-за операции неявной логики с номером столбца. Дополнительно применяется косвенное искажение, чтобы соединялись края получившейся сетки (4 «сплошных» областей, 4 «прозрачных» областей попеременно). Если луч пересекает одну из сплошных областей, цвет становится числом итерации (+смещением палитры в оттенках серого), а если нет, то плоскость смещается на указатель экрана, после чего процесс повторяется до достижения максимального числа итераций.

	mov cx,bp		; set inital point to time
	mov bx,-16		; limit to 16 iterations
fx3L:
	add cx,di		; offset point by screenpointer
	mov ax,819		; magic, related to Rrrola constant
	imul cx			; get X',Y' in DX
	ror dx,1		; set carry flag on "hit"
	inc bx			; increment iteration count
	ja fx3L			; loop until "hit" or "iter=max"
	lea ax,[bx+31]	; map value to standard gray scale

Sierpinski rotozoomer

Как демо Memories умещается в 256 байт - 14

sierpinski rotozoomer

Как демо Memories умещается в 256 байт - 15

График 1/cos(atan(x)), созданный с помощью www.google.com

Для поворота с масштабированием обычно требуются тригонометрические функции или их хорошие приближенные значения. Давайте взглянем на обычное уравнение 2D-поворота и интерпретируем его особым образом, чтобы избавиться от тригонометрии в вычислениях:

x' = x*cos(a) - y*sin(a)
y' = x*sin(a) + y*cos(a)

Если мы добавим масштабирование, то это будет выглядеть вот так:

x' = z * (x*cos(a) - y*sin(a))
y' = z * (x*sin(a) + y*cos(a))

А теперь давайте допустим, что мы не задаём z самостоятельно, и вынесем 1/cos(a) за скобки:

x' = 1/cos(a) * (x - y*tan(a))
y' = 1/cos(a) * (x*tan(a) + y)

Теперь заменим tan(a) переменной времени, потому что функция тангенса при приближении к 180° стремится к бесконечности:

x' = 1/cos(atan(T)) * (x - y*T)
y' = 1/cos(atan(T)) * (x*T + y)

Если нас не волнует отсутствие возможности задания коэффициента масштабирования и мы не управляем углом напрямую, то теперь можно выполнять повороты от -180° до +180° без использования тригонометрических функций. Вследствие этого коэффициент масштабирования оказывается привязанным ко времени T. Функция для коэффициента показана на изображении, благодаря ей масштабирование происходит от бесконечно малого числа до единицы (исходного размера) и обратно к бесконечно малому. Порядочное количество байтов было потрачено на украшение этого эффекта, исправление смещения времени, ускорения анимации, увеличения пикселей треугольников Серпинского и создание красивых цветов, но я считаю, что оно того стоило.

	lea cx,[bp-2048]; center time to pass zero
	sal cx,3		; speed up by factor 8!
	movzx ax,dh		; get X into AL
	movsx dx,dl		; get Y int DL
	mov bx,ax		; save X in BX
	imul bx,cx		; BX = X*T
	add bh,dl		; BH = X*T/256+Y
	imul dx,cx		; DX = Y*T
	sub al,dh		; AL = X-Y*T/256
	and al,bh		; AL = (X-Y*T/256)&(X*T/256+Y)
	and al,252		; thicker sierpinski
	salc			; set pixel value to black
	jnz fx4q		; leave black if not sierpinski
	mov al,0x2A		; otherwise: a nice orange
	fx4q:

Raycast bent tunnel

Как демо Memories умещается в 256 байт - 16

raycast bent tunnel

Это разновидность туннеля с рейкастингом из «into a new era» (см. выше). Подробное описание этого эффекта для похожей программы «Essence» было опубликовано на reddit. Я избавился от уникальных цветов, изменил направление наклона, а геометрия стала более замкнутой для повышения производительности на старых компьютерах и в DosBox.

	mov cl,-9		; start with depth 9 (moves backwards)
	fx5L: 
	push dx			; save DX, destroyed inside the loop
		mov al,dh	; Get Y into AL
		sub al,100	; Centering Y has to be done "manually".
		imul cl		; Multiply AL=Y by the current distance, to get a projection(1)
		xchg ax,dx	; Get X into AL, while saving the result in DX (DH)
		add al,cl	; add distance to projection, (bend to the right)
		imul cl		; Multiply AL=X by the current distance, to get a projection(2)
		mov al,dh	; Get projection(1) in AL
		xor al,ah	; combine with projection(2)
		add al,4	; center the walls around 0
		test al,-8	; check if the wall is hit
	pop dx			; restore DX
	loopz fx5L		; repeat until "hit" or "iter=max"
	sub cx,bp		; offset depth by time
	xor al,cl		; XOR pattern for texture 
	aam 6			; irregular pattern with MOD 6
	add al,20		; offset into grayscale palette

Ocean night to day

Как демо Memories умещается в 256 байт - 17

oceannight

Как демо Memories умещается в 256 байт - 18

oceanday

Эффект океана — превосходный пример «счастливой случайности». Если загрузить значение в FPU как integer и хранить его как число с плавающей запятой, а затем снова интерпретировать его как integer, то мы получим отличный паттерн. Если скомбинировать это с обратным делением, то получится красивый эффект волн. Он работает в тесной связке с регистром DX, у которого есть перемена знака в нужной нам позиции, поэтому мы можем легко может отделить небо от моря. Однако финальным штрихом становится расцветка. В соответствии со структурой глобального фреймворка значение AL определяется на входе функции, он содержит адрес эффекта. Немного перетасовав код, можно получить цвет неба «бесплатно», не используя инструкций, так же, как цвет «перехода в день», который тоже является адресом эффекта. И это не счастливое совпадение. В других версиях, отличающихся от версии для DosBox, цвет неба по этой причине может варьироваться.

	sub dh,120			; check if pixel is in the sky
	js fx6q				; quit if that's the case
	mov [bx+si],dx		; move XY to a memory location
	fild word [bx+si]	; read memory location as integer
	fidivr dword [bx+si]; reverse divide by constant
	fstp dword [bx+si-1]; store result as floating point
	mov ax,[bx+si]		; get the result into AX
	add ax,bp			; modify color by time
	and al,128			; threshold into two bands
	dec ax				; beautify colors to blue/black

Fading effect

Для удобства эффект изолирован от глобального фреймворка. По сути, он генерирует из указателя экрана псевдослучайное число, а затем выполняет смещение времени на его отмасштабированное значение, после чего вызывая нужный эффект.

	mov ax,0xcccd			; load magic Rrrola constant
	mul di					; transform screen pointer to X, Y
	add al,ah				; use transformation garbage as
	xor ah,ah				; pseudorandom value and clear AH
	add ax,bp				; add time value to random value
	shr ax,9				; divide by 512 (basically the speed)
	and al,15				; filter effect number
	xchg bx,ax				; move effect number to BX
	mov bh,1				; reset BH to align with start of code
	mov bl,[byte bx+table]	; read the effect address from the table

MIDI-музыка

Этот раздел кода выполняет инкремент значения времени и создаёт звук. Выбрав канал 3, мы можем повторно использовать инструкцию «смена инструмента канала» в качестве «RET». Выполняя сдвиг кода эффектов, можно создать подходящее значение для громкости, что позволяет сэкономить ещё один байт. Стоит учесть, что эта часть кода работает, только если MIDI-устройство уже находится в режиме UART, в противном случае нужно будет потратить ещё три байта. Многие из зрителей и организаторов говорили мне, что мелодия немного напоминает «Incantations» Майка Олдфилда, но создавалась она очень простой техникой. Со времён «Hypnoteye» 2015 года я экспериментировал с процедурным MIDI и в конечном итоге выпустил небольшой MIDI-фреймворк (64 байта). Основной принцип его работы заключается в прыжках с фиксированным шагом по пространству тонов и преобразовании высоких значений в более низкие (делением по модулю). Простыми сочетаниями stepwidth и modvalue можно достичь интересных эффектов. Например, stepwidth=3 позволяет получить постоянно длящийся сниженный минорный аккорд, а stepwidth=4 или stepwidth=6 — тритональные эффекты. При хорошо подобранном modvalue эти паттерны могут создавать последовательности. Я пока не проводил должного теоретического анализа, а просто исследовал пространство тонов и отмечал интересные звуки.

sounds: db 0xc3, 11, 0x93, fx2-s
...
		inc bp				; increment timing value
		test bp, 7			; play a note every 8th step
		jnz nomuse			; quit if in between
		mov dx,0x330		; port number for MIDI
		mov si,sounds		; adress for sound data
		outsb				; change instrument of channel 3
		outsb				; to vibraphone
		outsb				; play a note on channel 3
		imul ax,bp,-19*32*4	; the magic melody constant
		shr ax,10			; scale down and implicit "and 63"
		add al,22			; pitch base is 22
		out dx,al			; play THIS note on channel 3
		outsb				; play it with THIS volume

Полный код релиза

; "memories" by HellMood/DESiRE
; the tiny megademo, 256 byte msdos intro
; shown in April 2020 @ REVISION
;
;   (= WILL BE COMMENTED IN DETAIL LATER =)
;
; create : nasm.exe memories.asm -fbin -o memories.com
; CHOOSE YOUR TARGET PLATFORM (compo version is dosbox)
; be sure to use the dosbox.conf from this archive!
; only ONE of the defines should be active!
%define dosbox			; size : 256 bytes
;%define freedos		; size : 230 bytes
;%define winxpdos		; size : 263 bytes

; DON'T TOUCH THESE UNLESS YOU KNOW WHAT YOU'RE DOING
%ifdef winxpdos
	%define music
	%define switch_uart
	%define safe_dx
	%define safe_segment
%endif
%ifdef freedos
	%define safe_dx
%endif
%ifdef dosbox
	%define music
	;%define safe_dx ; sometimes needed
%endif

; GLOBAL PARAMETERS, TUNE WITH CARE!
%define volume 127	; not used on dosbox (optimization)
%define instrument 11
%define scale_mod -19*32*4; 
%define time_mask 7
%define targetFPS 35
%define tempo 1193182/256/targetFPS		
%define sierp_color 0x2A
%define tunnel_base_color 20
%define tunnel_pattern 6
%define tilt_plate_pattern 4+8+16
%define circles_pattern 8+16

org 100h
s:
%ifdef freedos
	mov fs,ax
	mov [fs:0x46c],ax
%endif
	mov al,0x13
	int 0x10	 
	xchg bp,ax
	push 0xa000-10
	pop es
%ifndef freedos
	mov ax,0x251c
	%ifdef safe_dx	
		mov dx,timer	
	%else ; assume DH=1, mostly true on DosBox
		mov dl,timer
	%endif
	int 0x21
%endif
top:
%ifdef freedos
	mov bp,[fs:0x46c]
%endif	
	mov ax,0xcccd
	mul di
	add al,ah
	xor ah,ah
	add ax,bp
	shr ax,9
	and al,15
	xchg bx,ax
	mov bh,1
	mov bl,[byte bx+table]
	call bx
	stosb
	inc di
	inc di
	jnz top
	mov al,tempo
	out 40h,al
	in al,0x60
	dec al
	jnz top
sounds:
	db 0xc3	; is MIDI/RET
%ifdef music
	db instrument,0x93
	%ifdef switch_uart
		db volume		; without switch, volume is in table
		db 0x3f 
	%endif
%endif
table: ; first index is volume, change order with care!		    					
	db fx2-s,fx1-s,fx0-s,fx3-s,fx4-s,fx5-s,fx6-s,sounds-s,stop-s
stop:
	pop ax
	ret
timer:
%ifndef freedos
	%ifdef safe_segment
		push cs
		pop ds
	%endif
		inc bp
	%ifdef music	
		test bp, time_mask
		jnz nomuse
		mov dx,0x330
		mov si,sounds
		outsb
		outsb
		outsb
		imul ax,bp,scale_mod
		shr ax,10
		add al,22
		out dx,al
		outsb
		%ifdef switch_uart
			inc dx
			outsb
		%endif
	%endif
nomuse:
	iret
%endif	
fx0: ; tilted plane, scrolling
	mov ax,0x1329
	add dh,al
	div dh
	xchg dx,ax
	imul dl
	sub dx,bp
	xor ah,dl
	mov al,ah
	and al,tilt_plate_pattern
ret
fx2: ; board of chessboards
	xchg dx,ax
	sub ax,bp
	xor al,ah
	or al,0xDB
	add al,13h
ret
fx1: ; circles, zooming
	mov al,dh
	sub al,100
	imul al
	xchg dx,ax
	imul al
	add dh,ah
	mov al,dh
	add ax,bp
	and al,circles_pattern
ret
fx3: ; parallax checkerboards
	mov cx,bp
	mov bx,-16
fx3L:
	add cx,di
	mov ax,819
	imul cx	 
	ror dx,1	 
	inc bx	 
	ja fx3L
	lea ax,[bx+31]	 
ret
fx4: ; sierpinski rotozoomer	
	lea cx,[bp-2048]
	sal cx,3
	movzx ax,dh
	movsx dx,dl
	mov bx,ax
	imul bx,cx
	add bh,dl
	imul dx,cx
	sub al,dh
	and al,bh
	and al,0b11111100
	salc				; VERY slow on dosbox, but ok
	jnz fx4q
	mov al,sierp_color
	fx4q:
ret
fx5: ; raycast bent tunnel
	mov cl,-9
	fx5L: 
	push dx
		mov al,dh
		sub al,100
		imul cl
		xchg ax,dx	
		add al,cl
		imul cl
		mov al,dh
		xor al,ah
		add al,4
		test al,-8
	pop dx
	loopz fx5L
	sub cx,bp
	xor al,cl
	aam tunnel_pattern; VERY slow on dosbox, but ok
	add al,tunnel_base_color
ret
fx6: ; ocean night / to day sky
	sub dh,120
	js fx6q
	mov [bx+si],dx
	fild word [bx+si]
	fidivr dword [bx+si]
	fstp dword [bx+si-1]
	mov ax,[bx+si]
	add ax,bp
	and al,128
	dec ax
fx6q:
ret

Бонус - NFO/ASCII

                                                             art : hammerfist
         ∂#MW%e                              _d$Ng,
         'B,  ∂b                   _jM@$QZb,cQ"  )@
  ,edRB$b,l@   Wk,yGR$KM&$b,     ,dP"     Wl ]bsd%UR8BG6&$@DSyG#ZKM&$b,
,dP      "T%L  'MGF      "*∂R_   Tg    "*4Zk,#I  YP   W"    7P      "*∂R
4M   gd@    ^   ∂@   d@b   dQ$#@Z@R3L_    "*GMj  'W      ,gd$   d@b   9Q$#%b
W#,  `M          Wb  `*  _4P   `Qk  *#N8L   `H5   @b   'QR7YK   `*  _4F"   Qk
`6@L             dML            '@          ,BK   'M    ∂B  *b,            '#L
  ^QBb,_     _,4&M∞∂@=,_       _dGL       _gQKM    GL    @k  'Mg,_         _dG,
    "*BN5W$2#MNP"   "*G3WRM8&B5P"`Y@QNW3Z5P" ∂#$W8BRM3XZN87    "*GW38M%EBDW5P"`


                              p r e s e n t s

            4
           d@,
         _& `Wl
      _,aP   "#baedM$#@@K JP*"?ML
 ,ad@$#P"         ,d@NEWVB"     X,aQPYb,_
V@Mm,_          ,d@MW#BW'      EMP"   '¶R ,ngBP^fML
 ¶M@N@y        Y#BNW#M"       J9"      `MQ9"      "MgRBq  ,QBMg,
  VN#P` ,d@@    `WM@^                   7f         ¶F` 7kY"   ^G  _.eQNE1.
   ]B _G@MWN$,   `P                     '     4b       QP      ¶w@F*^  ^Qb
   ]O@NRM#W@MNB,         ;                    ^`      j        JP^       Yl
  J#NRNWM@#BcT"^        ,A  _J                     _q@                   `X
 '¶WM#B@WdY`,7        _G#YN#PM                 _,gG"                      M,
  *BN#WP"  dK       ,Q@NRMB"]9       ,      _,M@Q*                        #A
   "U^      V@h,   iNBW#NT  J'      J9     s@QN"         _;               'D,
             ¶RMBv&NMQR@9  .W      .K'     "9`         ,6BA   _JL          ]l
              Y#NE@W#NRP   #[      `¶8               _d@MW#B_jW#W          BN
               "GQ@MR#W    QL_      *B            _,p#NBW#NQMG@WY          3Q
                  "Y@F     ,XW@M%im,_Yb_     _,g5@#MW@QMNE@E@NRMB         ,WM
                    `  _,gP*"#REM#GB@N#MQbnd@N#M@MW#R8QSB^'WQERM@        ;4NB,
                     ,GYKL    ¶E#B8R8QSB@M@#BM#W@MNB"`_  ,  "^` N       ,dW@Ql
                   _Q`'W`*t    '¶@GS#MBQ#E@W#NQBW[     'LvQ_   ,K    _dNABGM#N
                  ,F   '          `^WAB@QGE9*"9^*@L    jP7FY,  ¶h,_.jWM#BR#GBM,
                 J;    ,   _                  '       '   "LL  YxE#B8R8QSBNW@W;
                AP   _,Ag6^          _   J                  ¶A  `"Q#M@MW#R8E#P
               j@   `"XQW[            'LvK,_      'L_,/      @t    Y#NE@WNR"
              :M/     9^*@L           jP7F"       _PYKL     _,A;     ¶RSNQ"
              dKL     '     `        '   "L      "`'W`*t   `"XQb      `W^
              Q`8t            'L_,/         ,   _   '        9^Q
             ,W               _PYKL       _,Ag6^             ' W,     _ ,#N&
             !N  _   J       "`'W`*t     `"XQW[       _  J     N!_JG9^RwQ' *t
             `W,  'LvK,_        '        _gGB8@L   _   'LvK,_ ,WgB'    V    7L
         _.,gm&@B&wBZF"                j@'`  "WL _gML  jZd7Yb lN"          dBWl
      ,g&QB*"^`    `"*G@g, .gR&k,_   ,N"      '@QF  ¶k;gMF  *QvQ     jQ, ,@N@B#,
   .eQF*`              `Yb@"  "*6Qg,gF     ,   7     XMN"    'MNB,    ^¶QWSER@N;
 ,gP"           qy,      W'       ^Q'     &L      ,g@W'       `QMEL     `"WBNWP
g7              ¶9      ,X         M?     9"   _q8MSK           ¶EMt       *@K
Vh   _,m#L             _AH        le         ,GBDNE9^A,          *@F        NMg
 ¶L,qQ@ND           _.m@Bl        We      ,gM@B8#Q'   ¶h_                   lWE,
  W9NHW@`          JWM#B@]        @e     4WR@NGF^      'QL                  dRWl
   VMd*            "@BE@PM        'N      *UP"           VW,               JRSB;
  ,@F       j       `¶WK W,        ¶t                     XNt            _A@E#N
_JP       ,6&         "GLdM         XD,               _.g8NMA@k,_    _,gG#NMGR;
"Z      .JRER           'VMi     _jNB#W&_         _,j@E@W#Nl ¶MBGMNQGNQMG@QBW9
 ¶h   ,G@NRMBl            `"   ,d#R@M$F ¶Mg,_.gp&@@NEWVBWBMG  *QMN8R8SBN$E@WF
  Vb dW#R8QSRb,                 *YM@EQ,_ 'MENBW#NQMG#B@R@MW#l   "BM@QNENRQG'
   *WGS#MBMNEYL                    `^"*8M@Q@NRM#W@BWSNW@QBF"`     `^*@QBF^ [HFT]
    ^M@MW#Q9 ^Wt                           `^¶RQ@W8NQGP*`
     ¶Q#@P     Vk                            lA `"^`
      Y"       `MA                           J#,
                *R@,                        ,MQl
                 Y#Wk,                      GWM8L
                  W8RQSt,_                 AQ@MR#,
                  `@M@#SB@Mbm.,_          QNBW#NW
                    ¶QB8R8SBN$WNRM@#GNtwg@NMQR@B'
                     *MBQ#8R8QS@NE@WNBW#NQMG@NR;
                      `WGS#MBQ#R8QSB@NE@W#NQBW9
                        *OMW@QMNE@E@NRMW@QMB@*
                          `^"YQW@Q#SB#NE@EGP
                               `^"*8R@GBQF`

Автор: PatientZero

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js