![](https://habrastorage.org/webt/7u/dh/ww/7udhwwjcbgzix5jaxzonykfmcf8.png)
Изначально я не планировал делать демо на Chaos Constrictions 2018, однако за 2-3 недели до cc понял, что с пустыми руками идти на демопати никак нельзя, и решил написать небольшую демонстрацию для 386/EGA/DOS.
Скомпилировав в Turbo-C под DOS свою либу AnotherGraphicsLibrary, которая идеально ложиться в битплановую структуру EGA режима, я разочаровался, от тормозов, прежде всего тормозов EGA. Демо в том виде, в котором я хотел бы его видеть, за этот весьма ограниченный срок, сделать было невозможно.
Однако сдаваться и не делать что-либо, я уже не мог. И тут я вспомнил, что давно хотел принять участие в ZX-Spectrum конкурсах демо. А так, как за последний год у меня появилось целых два 48k реала, я мог получить определенное удовольствие от создания демо. К слову — для меня самое главное в написании демо это именно тестирование на реале, эмульгаторы не дают такого наслаждения от процесса, уж очень это замечательное чувство, когда после очередного изменения в коде ты закачиваешь демо на реал, и видишь как настоящая железка тасует байтики в памяти, отрисовывая эффект.
Поскольку из реалов у меня только 48k, то и демо я решил сделать для 48k. А из-за ограниченности сроков и отсутствия каких-либо наработок, выбор пал на создание 1k intro(демо объёмом всего 1 килобайт, или 1024 байта).
Последний раз z80 asm я тыкал в EmuZWin — замечательном эмуляторе со встроенным ассемблером. Но к сожалению EmuZWin на чем-либо выше Windows XP не работает, либо глючит.
Рассмотрев различные варианты остановился на связке программ Unreal+sjAsm+Notepad++, которые, на мой взгляд, по удобству сильно проигрывают EmuZWin, но в отличие от него живы.
Во время написания этого интро я вел, прямо в исходниках, лог разработки, по мотивам которого написан дальнейший текст:
Hello World!
Что надо писать первое, имея практически нулевой опыт в z80 asm? Правильно, вывод спрайта 5x5 знакомест или 40x40 пикселей, для одного из эффектов (по иронии, в дальнейшем, для того чтобы влезть в 1k эта недоделанная часть была выкинута из интро).
Поразительно, но это было довольно просто сделать с нуля, используя наперед сгенерированную табличку адресов строк с помощью Down HL.
Ох, индексные регистры, какие же они удобные, но какие меееедленные, буквально выжирают такты. Пришлось выкинуть их использование из кучи мест.
Еще здесь, в самом начале, я наткнулся на невероятные глюки sjAsm, точнее его последней версии. Дизасм в Unreal показывал абсолютно бредовую последовательность команд. Скачал предпоследнюю версию — с ней уже можно было хоть как-то жить.
![](https://habrastorage.org/webt/xh/yh/ot/xhyhotktsbfmg1iqk_gtrbmsvjy.png)
Понятное дело, что сколько-нибудь адекватное кол-во заранее нарисованных спрайтов в 1k не засунуть, поэтому я решил сгенерировать их динамически. Причем не абы как, а нарисовать с помощью полигонов.
Поэтому второй процедурой, которую я написал, стала процедура рисования треугольника. По большей части это было портирование своего-же кода, написанного на Си. С единственным глобальным отличием от Си версии — сначала генерируются сканалйны полигона, а только потом он рисуются по этим сканлайнам.
После высокоуровневых языков, получаешь определённое наслаждение от jr aka goto:
.sort_me_please:
ld de,(tr_x2)
ld bc,(tr_x0)
ld a,d
cp b
jr nc,.skip1
ld (tr_x2),bc
ld (tr_x0),de
.skip1:
ld de,(tr_x1)
ld bc,(tr_x0)
ld a,d
cp b
jr nc,.skip2
ld (tr_x0),de
ld (tr_x1),bc
jr .sort_me_please
.skip2:
ld de,(tr_x2)
ld bc,(tr_x1)
ld a,d
cp b
jr nc,.skip3
ld (tr_x2),bc
ld (tr_x1),de
jr .sort_me_please
.skip3:
Я был немного шокирован, тем, что у меня вышло в разумное время написать работающую draw_triangle на z80 asm, рисующую полигон пиксель в пиксель и без дыр при стыковке полигонов.
![](https://habrastorage.org/webt/tl/sv/or/tlsvorwso0wpqcrulnaewu23_84.png)
Hello triangles!
Частицы
![](https://habrastorage.org/webt/ng/kx/mq/ngkxmqez4envpqx6-tzzc0wd2ge.png)
Из-за наличия генератора табличкек строк экрана, я написал довольно кривую и медленную процедуру вывода точки, использующую эту табличку. Процедура имеет две точки входа — просто инверсия пикселя, и инверсия пикселя с закрашиванием его INK-а+BRIGHT+а цветом указанном в одном из регистров.
На этапе создания эффекта с частицами обнаружил что пример со структурами из примеров в wiki sjAsm просто не работает. Гугление вывело на тему с сайта zx-pk.ru, где описана эта проблема, и нет ее решения — ха, отлично — еще один глюк.
Решил сделать все четко — обновление координат независимо от отрисовки, по прерыванию. Ага… плюс дохрена байтов для генерации таблицы прерываний.
Частиц на этом этапе было немного, и они едва влезали в фрейм — это к слову о медлительности моей процедуры вывода точки %) Но использование общей таблицы со спрайтами, не давало мне ее выкинуть, и взять готовую, т.к. это сильно экономило место на необходимости только одного генератора таблиц. Да и моя любовь к велосипедам тоже :)
Слишком мало частиц… увеличил их количество, но теперь отрисовка разжирела до двух фреймов.
![](https://habrastorage.org/webt/tb/eu/qq/tbeuqqaoeef05uf04tfe33-t7f4.jpeg)
Тестирование на Peters WS64, который я добыл на прошлом cc и починил этой зимой :)
Кстати, уже на этом этапе точки превратились в жирные горизонтальные точки 2:1, как на Commodore 64. Вышло это из-за изначально малого кол-ва частиц, и моей неудовлетворённостью тем, что они были довольно незаметны при прогоне на реале. Решил проблему заменой таблички
db 128,64,32,16,8,4,2,1;
на
db 192,192,96,24,12,6,3,3;
что ухудшило точность позиционирования и сделало полет чуточку дерганым, но увеличило заметность. Тут на руку также сыграло еще и то, что частицы падали сверху вниз — по вертикали их размазывало зрение.
Частицы кстати падают каждая со своей случайной скоростью, а для хранения координат по Y используется два байта.
Спрайты
Выкинул недоделанный кусок части со спрайтами, поняв что не уложусь в 1k с ним.
Кроме того, уже ощущалась нехватка места, поэтому вспомнил про замечательную статью Интроспека про пакеры, выбрал zx7 в качестве пакера, что дало экономию примерно в 110 байт. Кстати возможно кто-то знает более подходящий пакер для 1k интро?
Chaos Constructions
![](https://habrastorage.org/webt/83/xj/h4/83xjh4wggmay449rwrmrfphx62y.png)
Поскольку у меня уже бала процедура вывода полигона, мне показалось крутой идеей разбить лого cc на полигоны и вывести на экран их друг за другом.
Написал некоторый тестовый код, выводящий несколько полигонов — все работало как я и задумал — отлично.
Для проверки того, влезет или нет моя задумка в 1k, нагенерировал некоторое кол-во рандомных полигонов, по прикидкам, достаточное кол-во для логотипа, и загнал в исходники. Скомпилировал, и убедился что — отлично — интро, в этом виде, влезает в лимит 1024 байта.
![](https://habrastorage.org/webt/fq/8k/bc/fq8kbcfvtdbdirtysojcd0hix8m.jpeg)
Лайф фото, узнаете девайс на столе? :)))
Решил еще раз протестировать полуфабрикат интро, уже с полигонами, и пакером, загрузил на реал и… получил сброс. Впервую очередь я стал грешить на то, что где-то забыл проинициализировать память, от чего, там, где на эмуляторе 0x00 и все отлично работает, на реале мусор, вызывающий сброс.
Ничего лучше, для нахождения проблемного места, чем метод половинного деления и di halt я не смог придумать.
Провозился со сбросом на реале в течении двух часов, локализовать глюк никак не выходило…
Как оказалось, дело было не в моем коде, дело было в включённом улучшайзере звука на телефоне с которого я грузил WAV-ки. Улучшайзер из потока битов в WAV файле генерировал поток бреда.
Как только я отключил его все волшебным образом заработало.
Обрисовал логотип в графическом редакторе errorsoft greenpixel, разбив его на кучу треугольников, и загнал вручную координаты в исходники. Запихнув лого Chaos Constructions полностью и запустив на реале — порадовался — выглядело довольно не плохо.
![](https://habrastorage.org/webt/vy/4m/ul/vy4mul7kzdo_o7qav3wtnvc3oki.jpeg)
Первое отображение лого на реале
Однако рандомных полигонов я напихал слишком мало, и на реальном лого, произошел выход за лимит 1k на 150 байт. И это при том, что эффект частиц был все еще не доделан, а переход между частями был резкий.
Лечь спать в этот день, из-за возни с глюками, вышло аж в 8 часов утра 8)
И да, я пытался оптимизировать размер, храня координаты и индексы вершин отдельно, но это сильно не нравилось пакеру, от чего размер только увеличивался.
Финал
![](https://habrastorage.org/webt/qv/h9/is/qvh9ist1fdq9xunbrd4uyejd3gi.png)
Придумал как разнообразить вывод логотипа, для этого не потребовалось почти ничего, кроме еще двух табличек пикселей:
fake_points1:
db 1,2,4,8,16,32,64,128; 1 ch
fake_points2:
db 32,8,128,2,16,1,64,4; 2 ch
normal_points:
db 128,64,32,16,8,4,2,1; 3 ch
Что дало прикольный эффект увеличения детализации отрисовки логотипа, или изначальной заблюренности и постепенного увеличения резкости.
И наконец, я сделал окончательную версию со всеми переходами, выкинув при этом кучу всего. В процессе нашел глюк в процедуре рисования треугольника — если у двух вершин координаты по Y одинаковые, то треугольник рисуется криво (похоже деление на 0 при вычислении dx), обошел временным хаком.
![](https://habrastorage.org/webt/xo/a3/--/xoa3--9_kt2k-uodr2gabj8ithm.jpeg)
Тестирование окончательной версии на Leningrad 48
Оптимизация размера
![](https://habrastorage.org/webt/-e/ro/l8/-erol8uszgdcler4i9jhp4n-9cu.jpeg)
Двузначные цифры — это «лишние байты»
94 лишних байта…
Пришла пора вырезать «культурное» сохранение/восстановление регистров у процедур на входе/выходе, далеко не везде это надо, а память жрет.
86 байтов…
Протестировал на реале — работает! Отбил еще немного памяти, попутно пофиксив баг с делением на 0 — 63 байта!
57 байт…
Добавил зацикливание.
random_store:
start:
Зацикливание кстати сделано на уровне распаковки, т.к. в качестве источника энтропии для ГСЧ использовались несколько байтов из кода инициализации (для экономии места), которые в процессе работы первой части портил ГСЧ. Поэтому для зацикливания, после окончания интро стоит ret, ну, а дальше — еще одна распаковка и переход к распакованному коду…
Избавиться от последних 48 байт не удалось никак, пришлось выпилить обработчик прерывания, но УРА! Запихал! Даже 1 лишний байт остался.
А раз нет прерываний, то можно забить на фреймовость, да и сложно увидеть сечение луча на одном пикселе в первом эффекте, поэтому увеличил кол-во частиц на глаз, с компромиссом между скоростью и зрелищностью.
Еще сильнее ужал, тупо переносом кода и данных из одного места в другое, помогая пакеру. Что заняло определенное время :)
Это освободило около 10 байт, в которые я засунул пародию на звук. Кхк, кхе, звук — это в некоторых местах, на слух вставленное:
ifdef UseSound
ld a,d
and #10
out (#FE),a
endif
Я не стал «прибивать гвоздями» звук, а сделал дефайн, получив таким образом две версии интро, со звуком и без. В ту, что без звука, в «лишние» байты запихал
db 'e','r','r','o','r'
Собрал trd и tap, и залил все это на сайт cc.
Ура — я участвую в демопати!
Послесловие
Со звуком вообще забавно получилось, кто-то на патиплейсе говорил про «четкий звук», кто-то странно на меня смотрел, а на pouet я обнаружил следующее:
![](https://habrastorage.org/webt/vt/p_/sw/vtp_swwrkskgpqbexsuj1nubn40.png)
И это:
![](https://habrastorage.org/webt/og/vh/wc/ogvhwc8p907emey1wk2erb-ejtm.png)
В общем, так я и не понял, понравился кому-либо 10-ти байтный звук или нет :)
И последнее — обидно что конкурс 1k в этом году так и не состоялся, работа на мой взгляд получилась достойная, но с 640k соревноваться сложно, а очень хотелось побороться.
Пишите демки, пишите 1k!
А вот и то, что в итоге получилось(пс, берегите уши):
Версия без звука: