Создание 1k intro Chaos для ZX-Spectrum

в 19:28, , рубрики: 1k intro, 48k, chaos constructions, demo, into, sjasm, Z80, zx spectrum, Демосцена, Работа с векторной графикой

Создание 1k intro Chaos для ZX-Spectrum - 1

Изначально я не планировал делать демо на 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 показывал абсолютно бредовую последовательность команд. Скачал предпоследнюю версию — с ней уже можно было хоть как-то жить.

Создание 1k intro Chaos для ZX-Spectrum - 2

Понятное дело, что сколько-нибудь адекватное кол-во заранее нарисованных спрайтов в 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, рисующую полигон пиксель в пиксель и без дыр при стыковке полигонов.

Создание 1k intro Chaos для ZX-Spectrum - 3
Hello triangles!

Частицы

Создание 1k intro Chaos для ZX-Spectrum - 4

Из-за наличия генератора табличкек строк экрана, я написал довольно кривую и медленную процедуру вывода точки, использующую эту табличку. Процедура имеет две точки входа — просто инверсия пикселя, и инверсия пикселя с закрашиванием его INK-а+BRIGHT+а цветом указанном в одном из регистров.

На этапе создания эффекта с частицами обнаружил что пример со структурами из примеров в wiki sjAsm просто не работает. Гугление вывело на тему с сайта zx-pk.ru, где описана эта проблема, и нет ее решения — ха, отлично — еще один глюк.

Решил сделать все четко — обновление координат независимо от отрисовки, по прерыванию. Ага… плюс дохрена байтов для генерации таблицы прерываний.

Частиц на этом этапе было немного, и они едва влезали в фрейм — это к слову о медлительности моей процедуры вывода точки %) Но использование общей таблицы со спрайтами, не давало мне ее выкинуть, и взять готовую, т.к. это сильно экономило место на необходимости только одного генератора таблиц. Да и моя любовь к велосипедам тоже :)

Слишком мало частиц… увеличил их количество, но теперь отрисовка разжирела до двух фреймов.

Создание 1k intro Chaos для ZX-Spectrum - 5
Тестирование на 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

Создание 1k intro Chaos для ZX-Spectrum - 6

Поскольку у меня уже бала процедура вывода полигона, мне показалось крутой идеей разбить лого cc на полигоны и вывести на экран их друг за другом.

Написал некоторый тестовый код, выводящий несколько полигонов — все работало как я и задумал — отлично.

Для проверки того, влезет или нет моя задумка в 1k, нагенерировал некоторое кол-во рандомных полигонов, по прикидкам, достаточное кол-во для логотипа, и загнал в исходники. Скомпилировал, и убедился что — отлично — интро, в этом виде, влезает в лимит 1024 байта.

Создание 1k intro Chaos для ZX-Spectrum - 7
Лайф фото, узнаете девайс на столе? :)))

Решил еще раз протестировать полуфабрикат интро, уже с полигонами, и пакером, загрузил на реал и… получил сброс. Впервую очередь я стал грешить на то, что где-то забыл проинициализировать память, от чего, там, где на эмуляторе 0x00 и все отлично работает, на реале мусор, вызывающий сброс.
Ничего лучше, для нахождения проблемного места, чем метод половинного деления и di halt я не смог придумать.
Провозился со сбросом на реале в течении двух часов, локализовать глюк никак не выходило…
Как оказалось, дело было не в моем коде, дело было в включённом улучшайзере звука на телефоне с которого я грузил WAV-ки. Улучшайзер из потока битов в WAV файле генерировал поток бреда.
Как только я отключил его все волшебным образом заработало.

Обрисовал логотип в графическом редакторе errorsoft greenpixel, разбив его на кучу треугольников, и загнал вручную координаты в исходники.
Запихнув лого Chaos Constructions полностью и запустив на реале — порадовался — выглядело довольно не плохо.

Создание 1k intro Chaos для ZX-Spectrum - 8
Первое отображение лого на реале

Однако рандомных полигонов я напихал слишком мало, и на реальном лого, произошел выход за лимит 1k на 150 байт. И это при том, что эффект частиц был все еще не доделан, а переход между частями был резкий.
Лечь спать в этот день, из-за возни с глюками, вышло аж в 8 часов утра 8)

И да, я пытался оптимизировать размер, храня координаты и индексы вершин отдельно, но это сильно не нравилось пакеру, от чего размер только увеличивался.

Финал

Создание 1k intro Chaos для ZX-Spectrum - 9

Придумал как разнообразить вывод логотипа, для этого не потребовалось почти ничего, кроме еще двух табличек пикселей:

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), обошел временным хаком.

Создание 1k intro Chaos для ZX-Spectrum - 10
Тестирование окончательной версии на Leningrad 48

Оптимизация размера

Создание 1k intro Chaos для ZX-Spectrum - 11
Двузначные цифры — это «лишние байты»

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 я обнаружил следующее:

Создание 1k intro Chaos для ZX-Spectrum - 12

И это:

Создание 1k intro Chaos для ZX-Spectrum - 13

В общем, так я и не понял, понравился кому-либо 10-ти байтный звук или нет :)

И последнее — обидно что конкурс 1k в этом году так и не состоялся, работа на мой взгляд получилась достойная, но с 640k соревноваться сложно, а очень хотелось побороться.
Пишите демки, пишите 1k!

А вот и то, что в итоге получилось(пс, берегите уши):

Версия без звука:

Автор: Error1024

Источник


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