Создание 1k intro Chaos для ZX-Spectrum
Изначально я не планировал делать демо на 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?
— Правильно, вывод спрайта 5×5 знакомест или 40×40 пикселей, для одного из эффектов (по иронии, в дальнейшем, для того чтобы влезть в 1k эта недоделанная часть была выкинута из интро).
Поразительно, но это было довольно просто сделать с нуля, используя наперед сгенерированную табличку адресов строк с помощью Down HL.
Ох, индексные регистры, какие же они удобные, но какие меееедленные, буквально выжирают такты. Пришлось выкинуть их использование из кучи мест.
Еще здесь, в самом начале, я наткнулся на невероятные глюки sjAsm, точнее его последней версии. Дизасм в Unreal показывал абсолютно бредовую последовательность команд. Скачал предпоследнюю версию — с ней уже можно было хоть как-то жить.
Понятное дело, что сколько-нибудь адекватное кол-во заранее нарисованных спрайтов в 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, рисующую полигон пиксель в пиксель и без дыр при стыковке полигонов.
Hello triangles!
Частицы
Из-за наличия генератора табличкек строк экрана, я написал довольно кривую и медленную процедуру вывода точки, использующую эту табличку. Процедура имеет две точки входа — просто инверсия пикселя, и инверсия пикселя с закрашиванием его INK-а+BRIGHT+а цветом указанном в одном из регистров.
На этапе создания эффекта с частицами обнаружил что пример со структурами из примеров в wiki sjAsm просто не работает. Гугление вывело на тему с сайта zx-pk.ru, где описана эта проблема, и нет ее решения — ха, отлично — еще один глюк.
Решил сделать все четко — обновление координат независимо от отрисовки, по прерыванию. Ага… плюс дохрена байтов для генерации таблицы прерываний.
Частиц на этом этапе было немного, и они едва влезали в фрейм — это к слову о медлительности моей процедуры вывода точки %) Но использование общей таблицы со спрайтами, не давало мне ее выкинуть, и взять готовую, т.к. это сильно экономило место на необходимости только одного генератора таблиц. Да и моя любовь к велосипедам тоже:)
Слишком мало частиц… увеличил их количество, но теперь отрисовка разжирела до двух фреймов.
Тестирование на 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
Поскольку у меня уже бала процедура вывода полигона, мне показалось крутой идеей разбить лого cc на полигоны и вывести на экран их друг за другом.
Написал некоторый тестовый код, выводящий несколько полигонов — все работало как я и задумал — отлично.
Для проверки того, влезет или нет моя задумка в 1k, нагенерировал некоторое кол-во рандомных полигонов, по прикидкам, достаточное кол-во для логотипа, и загнал в исходники. Скомпилировал, и убедился что — отлично — интро, в этом виде, влезает в лимит 1024 байта.
Лайф фото, узнаете девайс на столе? :)))
Решил еще раз протестировать полуфабрикат интро, уже с полигонами, и пакером, загрузил на реал и… получил сброс. Впервую очередь я стал грешить на то, что где-то забыл проинициализировать память, от чего, там, где на эмуляторе 0×00 и все отлично работает, на реале мусор, вызывающий сброс.
Ничего лучше, для нахождения проблемного места, чем метод половинного деления и di halt я не смог придумать.
Провозился со сбросом на реале в течении двух часов, локализовать глюк никак не выходило…
Как оказалось, дело было не в моем коде, дело было в включённом улучшайзере звука на телефоне с которого я грузил WAV-ки. Улучшайзер из потока битов в WAV файле генерировал поток бреда.
Как только я отключил его все волшебным образом заработало.
Обрисовал логотип в графическом редакторе errorsoft greenpixel, разбив его на кучу треугольников, и загнал вручную координаты в исходники.
Запихнув лого Chaos Constructions полностью и запустив на реале — порадовался — выглядело довольно не плохо.
Первое отображение лого на реале
Однако рандомных полигонов я напихал слишком мало, и на реальном лого, произошел выход за лимит 1k на 150 байт. И это при том, что эффект частиц был все еще не доделан, а переход между частями был резкий.
Лечь спать в этот день, из-за возни с глюками, вышло аж в 8 часов утра 8)
И да, я пытался оптимизировать размер, храня координаты и индексы вершин отдельно, но это сильно не нравилось пакеру, от чего размер только увеличивался.
Финал
Придумал как разнообразить вывод логотипа, для этого не потребовалось почти ничего, кроме еще двух табличек пикселей:
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), обошел временным хаком.
Тестирование окончательной версии на Leningrad 48
Оптимизация размера
Двузначные цифры — это «лишние байты»
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 я обнаружил следующее:
И это:
В общем, так я и не понял, понравился кому-либо 10-ти байтный звук или нет :)
И последнее — обидно что конкурс 1k в этом году так и не состоялся, работа на мой взгляд получилась достойная, но с 640k соревноваться сложно, а очень хотелось побороться.
Пишите демки, пишите 1k!
А вот и то, что в итоге получилось (пс, берегите уши):
Версия без звука: