Pull to refresh

Демо BACK TO THE PET — дневник разработки

Level of difficultyMedium
Reading time54 min
Views1.8K

В 2022 году я написал и выставил на CAFe 2022 полноформатное демо для редкой в наших краях платформы, одного из древнейших персональных компьютеров — Commodore PET 4032. Монохромный текстовый режим 40x25 без возможности загрузки шрифта, никаких аппаратных скроллов, однобитный бипер на выходе последовательного порта, 32 килобайта ОЗУ, в которые помещается все 4 минуты демо без дозагрузок.

По итогам года демо попало в список The Meteoriks 2023, было номинировано на Best Oldschool Production (лучшее демо для ретро-платформ), где, конечно, не победило, но составило конкуренцию другой работе, в создании которой я также принимал участие, и о чём рассказал на Хабре ранее.

Ещё до начала проекта я решил не делать традиционный 'making of' после релиза, чтобы его написание не затянулось на год — тяжело работать над новыми проектами и одновременно мучительно пытаться вспомнить, что, как и зачем я делал в предыдущем месяцы назад. Вместо этого я в очередной раз попытался вести полноценный дневник разработки, записывая мысли непосредственно в рабочем процессе, от возникновения идеи до результата, и в кои-то веки удалось довести документируемый таким образом проект до завершения.

Записи до 06.09 сделаны по памяти, до того момента я ещё не определился, что и в каком формате буду делать. Осторожно: далее следует плохо структурированное чтиво эпических размеров, в том виде, в котором оно записывалось, без литературной обработки. Для Хабра решил не разбивать на части, но дополнить иллюстрациями.

06.08

Посетил ежегодный местный ретро-геймерский конвент GBX Summer Party. Встретил там коллегу, и он сообщил мне, что в этом году снова будет CAFe в Казани. Призадумался, а не посетить ли его. Ну а если посещать — то не с пустыми руками, нужно сделать какую-то работу.

13.08

Среди множества заметок о возможных проектах появляется запись идеи реализовать эмулятор PET на внутренних ресурсах ESP8266, без внешней обвязки, для встраивания в клавиатуру. Этой идее несколько месяцев, она периодически приходила в голову до этого.

14.08

У меня накопился с десяток идей для возможных демо, в разных форматах и для разных платформ, включая самую дичь. Часть из них уже была описана в общих чертах. Набросал краткие описания других идей, и попробовал оценить, какую из них можно было бы успеть реализовать за имеющееся время. Точно не помню, но кажется идея демо для PET возникла где-то в этом процессе, изначально её в списке идей не было, видимо в связи с мыслями об эмуляторе PET. Преимущество этой задумки в том, что на создание сложных эффектов нет времени, но на PET их сделать сходу всё равно не получится, за отсутствием графического режима. Значит, можно будет ограничиться относительно простыми псевдографическими эффектами, а основной упор сделать на эстетику платформы. Также есть готовое решение по звуку в виде PeskyTone/PeskySound и плагина для 1tracker, и вообще некоторый относительно недавний опыт — всё это должно позволить уложиться в срок.

15.08

У The 8-bit Guy выходит видео про самодельный корпус для реплики PET. Вероятно это утвердило меня в мысли выбрать именно направление PET.

18.08

Хотя у меня уже есть готовое решение для музыки, оно заточено на минимизацию размера данных в ущерб разнообразию звучания, и процесс сочинения в рамках этой системы относительно трудоёмкий. Хотелось бы иметь возможность использовать для сочинения Reaper — он показал себя очень удобным и экономящим время инструментом в ряде предыдущих проектов, начиная ещё в AONDEMO. Для этого я решил модернизировать мой VSTi плагин PCSPE, а в процессе также решил выделить эту модернизацию в отдельный проект под названием PETCB2. Это то же самое, но с огибающей для формы волны и с другим выходным форматом данных. Изменения были минимальными, и я завершил этот проект за день, включая пример плеера на 6502.

Интерфейс плагина PETCB2
Интерфейс плагина PETCB2

Работа над кодом плеера стала разогревом перед началом работы над демо — я настроил окружение, подготовил конфиг и сборочные файлы, подтянул эмуляторы.

19.08

Написал пакер для выхлопа PETCB2, который позволит сэкономить значительный объём ОЗУ без особых потерь скорости. Демо планируется однозагрузочным, оно должно влезать в 32 килобайта вместе со всеми буферами, потому что PET сразу установил традицию утомительно долгой загрузки на многих компьютерах Commodore. Если на C64 справлялись с этим за счёт кастомных загрузчиков, на PET сделать динамичное демо с подгрузками и непрекращающейся музыкой на нём не представляется возможным, особенно с учётом плохой эмулируемости и недоступности оригинального железа.

На основе кода примера плеера из PETCB2 создал пустой проект для демо. В отличие от многих моих предыдущих проектов, код демо будет писаться полностью вручную на ассемблере, так как производительность PET и объём доступной памяти очень ограничены, компилированный C код тут не прокатит.

21.08

Уже некоторое время набрасывался предварительный список идей для эффектов, новые идеи приходили во время работы, и к этому моменту стало оформляться примерное содержание. Внятного сценария пока нет, работа над ним только начата, но появляется предварительная оценка динамики — порядка трёх минут, чтобы не утомить публику довольно однообразной псевдографикой, и порядка десяти сцен, около 20 секунд на сцену.

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

22.08

Сделал набор символов PETSCII в виде тайлсета для NESST, так как рисовать мне удобнее в нём.

Набор символов и часто используемые символьные элементы для последующего копипаста. Сам редактор моего же авторства предназначен для графики NES, но часто пригождается и для других платформ.
Набор символов и часто используемые символьные элементы для последующего копипаста. Сам редактор моего же авторства предназначен для графики NES, но часто пригождается и для других платформ.

23.08

Начал реализовывать эффект символов Матрицы на ассемблере 6502, и понимаю особенности платформы. Эффекты хочется делать по возможности в 60 FPS, так как они смотрятся значительно интереснее, но на PET с его тактовой частотой 1.0 МГц при 1000 байтах экрана получается порядка 16 тактов на обработку каждого байта экрана за кадр, то есть времени на полную перерисовку экрана впритык. Эффект тормозит.

Прихожу к мысли, что лучше все эффекты прототипировать на C в окружении SDL, и когда алгоритм достаточно оптимизирован, тогда уже переписывать его на ассемблер 6502. Ранее я применял этот подход при создании HEOHDEMO, и он хорошо сработал, это должно сэкономить время. Сделал прототип эффекта Матрицы в таком виде.

24.08

Эффект Матрицы готов и настроен для плавной работы.

05.09

Написал C-прототип задуманного немного ранее эффекта волн на горизонтальных полосках — вариация того, что запомнилось в No Pets Allowed, отладил алгоритм. Начало и конец эффекта, которые представляют собой мини-эффекты, пока отсутствуют. Также нет полного понимания, как именно добиться 60 FPS в этой сцене — только общая идея, но добиться надо.

Сценария всё ещё нет, но список эффектов, которые ближе к практической реализации, постепенно увеличивается. Другие эффекты отпадают после оценки сложности. В частности, была идея эффекта со змейкой, быстро собирающей точки и заполняющей весь экран. После изучения алгоритмов, решающих змейку, выяснилось, что им нужно порядка 60 тысяч шагов для поля 30x30, и настолько эффективный алгоритм реализовать не получится, а заранее просчитанная анимация просто не влезет в ОЗУ.

06.09

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

Задумал простую сцену-филлер с шахматным полем 2x2 символа, где отдельные строки и столбцы будут попиксельно скроллиться относительно других строк и столбцов. Получить в ней 60 FPS должно быть достаточно просто, т.к. нужно просто размножить одни и те же четыре байта из таблицы на две строки или столбца.

Поприкидывал в NESST, выйдет ли сделать ранее задуманную сцену лабиринта — развитие известной в Commodore-кругах простейшей однострочной программы.

Придумал сцену с речевой вставкой, чтобы на экране было некое лицо и оно произносило какую-то фразу. Лицо должно быть в духе страшной рожи из советской аркады Магистраль, а фраза — какой-то слоган Commodore, произносимый речевым синтезатором в духе ранних 80-х. Выбрал известный слоган про Амигу. Прикинул по памяти, и решил, что заморачиваться синтезом из аллофонов нет смысла, трёхсекундная фраза и так должна поместиться в 3-4 килобайта памяти. Перебрал несколько эмуляторов SP0256, Speak&Spell, в итоге нашёл годный VSTSpeek, эмулирующий S.A.M, и что важно, поддерживающий фонетический режим. Сконструировал из фонем фразу, подрезал её нужным образом.

Поискал идеи для рожи с помощью Stable Diffusion, но в итоге пришёл к идее нарисовать сам PET псевдографикой вручную, а лицо представить просто крупными пикселями глаз и рта на экране нарисованного PET. С двух попыток нарисовал этот экран в NESST. Прикинул, как сделать липсинк с технической точки зрения, почитал про раскадровки рта в классической анимации.

Под конец дня подумал, что стоило бы вести дневник разработки. Я уже не раз пытался делать это для разных проектов, но они в итоге не доходили до завершения. С другой стороны, писать making of постфактум довольно утомительно и лень, а детали не задерживаются в памяти, и восстановить процесс прихода к тем или иным решениям иногда уже не получается. Восстановил в памяти и записал предшествующие события.

07.09

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

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

Взялся за голосовую вставку. Вчера подобрал фонетический ввод для VSTSpeek: OHNLIH KAAMOHDOHR PPAEAEAETTT MEYKS IHIHT PAWSIHBLLLLL. Часть с AW звучала некорректно, но начальная гласная мне понравилась больше, и я подрезал итоговый сэмпл для корректного звучания. Тестовый импорт сэмпла я предварительно делал в 1tracker в движке SquatM, новые тесты в BeepFX. Выяснилось, что пропадает звук S и не очень чётко звучат некоторые другие, из-за их смещения относительно нулевого уровня. Пододвинул и сделал громче некоторые места в Wavosaur, и итоговый трёхсекундный сэмпл зазвучал адекватно. Более-менее разборчивый звук получается при размере сэмпла 3.5 килобайт (сжимается apultra до 2.6K), в меньшем качестве теряются свистящие. Высокое качество звука для этой сцены не требуется, главное общий эффект: напомнить, как впечатляли первые голосовые вставки в компьютерных программах. Сделал проигрывание сэмпла, минимальную анимацию рожи и печать фразы снизу во время проигрывания. Фоновая картинка пока не выводится, пока не придумал эффекты для её появления и стирания.

Чтобы уместить все эффекты в имеющейся памяти, нужно сделать их как можно компактнее. Предполагаю задействовать сжатие сцен целиком, вместо со всех их кодом и ресурсами, и распаковку по мере надобности. Сжимать буду apultra. Есть готовый депакер для 6502, но он под другой ассемблер, требуется адаптация. Объём доступной памяти также ограничивает количество эффектов в демо, что с одной стороны неудобно, но с другой позволит удержать объём проекта в рамках приличий, чтобы можно было закончить его вовремя.

08.09

Адаптировал aplib_6502.s к синтаксису CA65. Проверил пока только на картинке с компьютером, но ошибок быть не должно.

Уже некоторое время обдумывал сцену с классическим phong bump mapping. Помимо скорости, основная проблема в том, как отобразить освещение на PET, чтобы это смотрелось интересно. Набросал прототип на C с вариантом, на который делал основную ставку — горизонтальные линии и пиксельные смещения в каждом знакоместе в зависимости от освещённости — результат не смотрится. Возможно, попробую преобразовать эту идею в морфинг выпуклых изображений.

09.09

Провёл эксперимент с прототипом бампа, заменив отображение с линий на распределённые по плотности символы. Смотрится поинтереснее, но SDL-прототип работает на 60 FPS, а на PET хорошо, если получится выжать 30, но скорее будет 20. Для желаемого эффекта нужно обеспечить 256 градаций псевдояркости, тогда как типичная оптимизация для 8-битных платформ подразумевает всего 15 градаций, чтобы уложить основные расчёты в 256-байтную таблицу. В таком виде задача обещает быть сложной, поэтому отложена на потом.

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

12.09

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

Для финала этой сцены сделал ещё один микроэффект, гашение экрана псевдояркостью. Вероятно он также пригодится при запуске демо для стирания текущего содержания экрана. Для появления экрана я сначала думал сделать разнонаправленный скролл частей экрана, но это визуально слишком простой эффект. Вместо него решил сделать посимвольную прорисовку картинки. Порядок появления символов я определил вручную, попиксельной анимацией маски в Graphics Gale. Вышло 119 кадров. С помощью очередной вспомогательной программки-конвертера преобразовал анимацию маски в набор данных в формате три байта на символ, адрес и код символа.

Также сделал появление лица на экране несколькими фазами увеличения его из точки в центре до нормального размера.

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

В процессе отладки сцены, после очередного прослушивания фразы, элементы паззла в виде разрозненных идей наконец сложились, и оформилась в общем-то очевидная идея для центральной концепции демо: реклама PET, ретрохайп платформы. Это определило содержание изображений для сцены с морфингом выпуклых фигур, написал её прототип и нашёл более-интересный вариант отображения четырёх картинок с размытием и инверсией выпуклости-впуклости.

Исходные карты высот для сцены с морфингом.
Исходные карты высот для сцены с морфингом.

13.09

Дальнейшее обдумывание концепции и вариантов названия. Рабочее название PETDEMO слишком простое, но оно неплохо сочетается с названиями моих предыдущих работ. Учитывая направленность демо, есть вариант PET*HYPE. Разумеется, в голову приходили также различные гэги про PETting, но это слабовато. Вполне однозначно ясно, что хотелось бы в том или ином виде включить PET в название.

Возникла одна довольно спорная идея, которая, впрочем, является давней традицией демосцены, и по прежнему не сдаёт позиций — сделать отсылку к такой глыбе поп-культуры, как фильм Назад в будущее, и назвать демо Back to the PET. Совмещая это с идеей хайпа, подразумевается, что это демо могло бы существовать во времена появления платформы, и служить её рекламой. В минусах неоригинальность подхода, традиционные плюсы — расположение более массового зрителя за счёт узнаваемости, элементы для сценария и цитат в контенте. За неимением лучших концепций пока склоняюсь к этой идее.

Начал реализацию сцены морфинга на ассемблере. Сделал сохранение карт высот в формате RLE (три бита высота, пять бит кол-во повторов) и распаковщик на 6502 — смысл этого в экономии памяти под буфера в момент работы эффекта. Также реализовал прочие необходимые элементы, но пока сцена смотрится очень плохо, особенно сама анимация морфинга. Из-за нехватки скорости я попытался использовать оптимизацию в виде обновления экрана по столбцам, но из-за неё происходит мерцание и диагональные разрывы. Придётся переделать, и возможно ограничиться 30 FPS.

14.09

Разобрался со всеми проблемами эффекта морфинга. К сожалению, его плавность оставляет желать лучшего — 20-30 FPS в зависимости от разницы между изображениями. Для избежания сечения с лучом пришлось разделить отрисовку экрана и цикл морфинга, что автоматически снижает скорость эффекта почти вдвое — и даже так едва-едва хватает тактов, чтобы сечения не происходило, и это ещё без музыки. Конечно, можно было бы получить 60 FPS при наличии лишних 8 килобайт ОЗУ для предварительного просчёта всех кадров, но я хочу уложиться в 32 килобайта на всё демо, и это обещает быть довольно непростым делом. Видимо, придётся смириться с недостаточной плавностью. Тайминг самого демо не должен пострадать из-за неравномерной скорости эффекта, так как смена картинок планируется по маркеру в музыке, на каждый удар.

Для завершения сцены морфинга нужен какой-то эффект стирания экрана. Разъезжание или осыпание полосок было бы слишком банальным, поэтому я решил попробовать сделать дополнительный эффект внутри этой сцены: плавное стирание по форме последнего изображения, сердечка. В процессе также пришла идея сделать это со шлейфом символов меньшей плотности. Реализовал эффект следующим образом: нарисовал покадровую анимацию в разрешении 20x25 (так как она симметрична) в Graphics Gale, написал конвертер, выдающий для каждого кадра набор адресов символов для стирания в каждом кадре. Понадобилась одна маленькая хитрость: так как ширина экрана на PET 40 символов, сделать симметричную зарисовку простыми битовыми манипуляциями не получится. Пришлось отдать 10 старших бит слова на хранение вертикального смещения в буфере (Y*40), и младшие 6 бит на горизонтальное смещение. Таким образом легко посчитать адрес символа в обоих половинах экрана. Итоговый эффект смотрится эффектно, хотя и не совсем в стиле сцены. Оставлю пока так.

На доделку даже такой в общем-то примитивной сцены ушёл целый день. Но хотя бы счётчик готовых сцен перевалил за две единицы.

15.09

Среди первых задумок для демо была идея обыграть классическую однострочную программу на Commodore BASIC, рисующую лабиринт случайным чередованием символов / и \. Решил сделать прототип и посмотреть, работает ли изначальная задумка: пустить несколько лабиринтов разного размера в виде слоёв с разной скоростью прокрутки. Оказалось, что задумка не работает, слои сливаются в кашу, трудно отличимую от стандартного лабиринта. Но прототип подсказал другой вариант: сначала рисуем классический лабиринт, потом более крупный и более детализированный. Попробовал сделать версию с тайлами 4x4 символа.

С появлением новых идей и с пониманием, какие эффекты ближе к практической реализации, обновил и уточнил сценарий. Текущая его версия уже более-менее похожа на правду, она содержит десять сцен разной степени сложности. Также есть восемь возможных дополнительных сцен, которые могут быть включены, если удастся сделать их вовремя. Готовность же кода и контента самого демо пока можно оценить только в 10%. Текущая задача прежняя — довести как можно больше сцен до готовности за минимальное время, чтобы как можно раньше появилось то, из чего можно собрать хоть какое-то демо.

Одна из новых сцен, запланированных в сценарии, изначально предполагала просто показ надписи Personal Electronics Transactor, или очень крупной с быстрым горизонтальным скроллом, или же по одному слову, с какими-то простыми эффектами. В процессе поиска идей я случайно нагуглил несколько обложек журнала Commodore Computer Club, на которых часто изображались прохладно одетые красавицы, и это напомнило мне рекламу в компьютерных журналах 70-х — это часто было изображение женщины, взаимодействующей с компьютером — традиция, продолжившаяся и на демосцене с 80-х до наших дней. Так возникла задумка добавить в сцену с надписью три псевдографические картинки, изображающие женские рекламные образы и эффект печати каждого слова — более разнообразный визуальный контент и лишние голоса от мужской аудитории.

Однако, изобразить в монохромном PETSCII 40x25 символов женскую фигуру крайне непросто, а на достижение уровня мастера в этой области времени нет. У меня есть тайл-матчер в NES Screen Tool, и я потратил несколько часов на попытки получить сколько-либо приличную конверсию. Эксперименты показали, что полутоновые фотографии конвертируются крайне плохо, но карандашно-векторные зарисовки, в частности, получаемые через онлайн-стилизаторы, дают более-менее узнаваемый результат. Они всё равно выглядят очень абстрактно-случайно, что стилистически не бьётся с вручную нарисованной сценой говорящего компьютера и, вероятно, с будущим титульным экраном, и я сильно сомневаюсь, что такое сочетание будет смотреться достаточно органично. Впрочем, выбора всё равно нет, обеспечить хорошую стилистически выдержанную PETSCII-псевдографику я сейчас всё равно не смогу.

Исходник, стилизация перед конверсией, результат конверсии.
Исходник, стилизация перед конверсией, результат конверсии.

16.09

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

Согласно нынешней моде, попробовал сгенерировать подходящие изображения для сцены с Persona Electronics Transactor нейросетью Stable Diffusion, но у неё плохо получается этот сюжет. Раз уж всё равно придётся брать изображения на стороне, решил взять их из той самой рекламы в старых компьютерных журналах. В качестве эффекта для сцены я решил сделать анимацию вращения крупных заглавных букв и печать полных слов обычными символами — традиционная для демосцены композиция 'женщина и говнолёт'. Казалось бы, примитивно, но в рамках платформы даже анимация вращения уже достаточно непростой эффект — её надо убедительно нарисовать псевдографикой, и как-то утрамбовать в память.

Анимацию буквы я начал рисовать в NESST, там есть очень примитивная система переключения между несколькими картами тайлов в пронумерованных файлах. Первые пробы пера показали оптимальный размер буквы — 11 на 10 символов (13 символов в ширину для анимации граней). Экспериментальным путём выяснилось, что нужно 64 фазы вращения, из которых 32 рисуется вручную, и 32 генерируется таблицей отзеркаливания символов. Одна буква занимает 130 байт, 32 кадра — 4 с небольшим килобайта, и таких букв три, плюс ещё 3 килобайта псевдографических картинок. Это почти половина всей доступной памяти, поэтому однозначно потребуется какое-то дополнительное сжатие, помимо сжатия сцены целиком. Даже не хочется думать о последующей сборке всех сцен демо, геморрой обещает быть знатным.

После первых попыток стало понятно, что нарисовать вручную вращение псевдографикой 'на глаз' в принципе можно, но мелкий джиттер значительно портит впечатление, и я решил прибегнуть к старому трюку: смоделировать математически точное вращение в Blender, а потом воссоздать правильные смещения граней в псевдографической анимации вручную. Фактически я сконвертировал анимацию: смоделировал и отрендерил все три буквы в 3D в точном пиксельном размере, строго по сетке, в ортографической проекции, без шейдинга, с разными цветами граней. Моделирование делал по давно придуманному рецепту: создаётся plane, делается subdiv на количество предполагаемых пикселей, далее лишние полигоны-пиксели удаляются, делается extrude. Задаётся точный размер рендера, устанавливается орто камера сверху точно по центру, её масштаб подгоняется до касания plane краёв прямоугольника камеры. Рендер немного доработал руками в Gale, добавив на торец вертикальные линии таким образом, чтобы соответвовать имеющимся символам псевдографики, перевёл в два цвета, и импортировал в NESST тайл-матчингом к набору PETSCII. Получилась реально сконвертированная 3D анимация на PET.

Подготовка анимации буквы в Blender.
Подготовка анимации буквы в Blender.

17.09

Определился со способом хранения анимации вращения букв. Все коды символов утрамбовываются в минимальное количество бит — всего 23 символа, значит на один символ хватает 5 бит. Таблица 23-х кодов символов сохраняется, к ней в пару идёт таблица кодов визуально зеркальных символов, для получения программно развёрнутых по горизонтали копий всех кадров. Три оставшиеся бита кодируют 1..8 повторов символа. Стоп-кода нет, так как длина блока данных известна. Буквы кодируются по столбцам. Это позволило утрамбовать 10 килобайт в 3, и они должны неплохо дополнительно сжиматься LZ пакером. Написал конвертер данных, протестировал распаковку в прототипе на SDL. Написал распаковку и вывод анимации для самого PET. Работает на все 60 FPS, что неудивительно.

18.09

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

Посетил второй день Яндекс Демодуляции, где обсудил с несколькими заинтересованными лицами возможность посещения CAFe. В разговорах упоминал о том, что готовлю работу, но не раскрывал никаких деталей. Это напомнило мне, что времени до пати осталось не так уж много, а работы по демо ещё непочатый край.

19.09

Сделал RLE-упаковщик картинок для сцены с буквами, написал скролл картинок и эффект символьного шума на ассемблере для PET. Скроллится только область, в которой есть изображение, поэтому весь эффект работает на 60 FPS.

Набросал алгоритм появления и исчезания слов по буквам в прототипе на SDL. Мне понравилось, как он выглядит при очень небольших значениях максимальной дистанции удаления букв, на 2-3 символа. Хороший вариант для секции с приветствиями.

20.09

Согласно известному принципу 80/20, доделываю те самые рутинные 20 процентов для сцены с буквами, которые растянулись на 80 процентов времени. Сцена в принципе завершена, если не считать сомнительных картинок, и это даёт около трёх завершённых сцен в демо.

21.09

После начальной реализации задуманного в полном объёме, я немного упростил сцену для улучшения динамики: изначально надписи под вращающимися буквами не только посимвольно прилетали, но и улетали, а картинка с женским силуэтом при смене экрана скроллилась обратно в ту же сторону, откуда приехала. Я убрал улетание букв и сделал 60 FPS скролл всех картинок вниз.

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

Так как сцена состоит из трёх экранов, а для синхронизации с музыкой лучше работают четыре такта, чем три, возникла идея дополнить её ещё одним элементом — показать все три вращающиеся буквы одновременно, уже без фоновой картинки. Однако, скорости работы имеющегося кода не хватает на распаковку трёх букв на скорости 60 FPS. Путём многократного искривления извилин сделал серию оптимизаций в коде распаковки и отрисовки вращающейся буквы, и всё-таки, казалось бы, уложил все три буквы в один кадр. НО только для PAL, то есть в 50 FPS.

Не знаю, по каким причинам, но WinVICE упорно эмулирует только PAL модели, хотя предположительно они были очень редки. Тестирую же я изменения в основном именно в VICE, хотя там и не та модель PET — потому что он не требует набирать вручную RUN при загрузке из PRG-файла и нажимать дополнительные кнопки, и тест можно запустить гораздо быстрее. К тому же у MAME по умолчанию просто гигантское окно, заслоняющее нужные вещи. К сожалению, после теста на NTSC-машине в MAME оказалось, что скорости всё равно не хватило — у PAL-моделей 1000000/50 тактов на кадр, а у NTSC — 1000000/60. При записи дробью разница не так бросается в глаза, но по факту это 20000 против 16666 тактов на кадр, то есть прилично меньше (на 17%). Боюсь, что и в предыдущих сценах с этим будут проблемы. Делать PAL only демо или откатываться до 30 FPS для NTSC не хотелось бы. Пока не знаю, как я буду решать эту проблему. Видимо, придётся как-то урезать осетра.

22.09

Успев расстроиться, взял паузу, а потом сделал ещё один подход к трём буквам. Возникла ещё одна идея для оптимизации, и она выжала недостающие проценты производительности. Правда, теперь немного рвётся экран во время одновременного вращения всех букв, потому что двойной буферизации нет, и буфер под распаковку кадра буквы один, для экономии и памяти и скорости. Но это уже приемлемая шероховатость. Дополнил сцену согласно задумке — прилёт линий, печать надписи. один оборот всех букв одновременно вокруг оси, гашение через уменьшение плотности пикселей в символах. Вопрос подтормаживания остальных элементов этой и других сцен в NTSC ещё предстоит решить.

Решил не делать внутри демо финальные титры, для экономии памяти и структурного разнообразия. Вся информация пойдёт в readme, а краткий кредит я встроил в эффект матрицы — там теперь печатаются надписи, а волны падающих символов их стирают. Наконец-то нашлось подходящее место, чтобы ввернуть пришедшее в голову некоторое время назад словосочетание 'proper demo'. Оно забавно звучит на русском, и этим звучанием иносказательно подразумевает, что попытка выжать из платформы невыжимаемое не очень успешна — такой хулиганский easter egg. Это было очередное небольшое изменение в сценарии, который с предыдущего упоминания уже практически зафинализировался, и получает только такие незначительные дополнения.

Теперь можно сказать, что вполне уверенно готовы четыре сцены из (теперь уже) одиннадцати. Одну сцену из дополнительного списка я решил добавить в обязательном порядке хоть в каком-то виде, чтобы усилить сюжетную линию. Она очень хорошо встаёт в сложившийся сценарий, повышая его связность, тогда как другие идеи из дополнительного списка представляют собой просто рандомные эффекты. В процентах завершённость работы пока всё равно невелика, примерно 20-25. Есть концепция, сценарий, название, часть эффектов и контента. Нет ещё более чем половины сцен, нет музыки и сборки. К вопросу о последней, обдумал запасной план с реализацией подгрузок на вполне вероятный случай невлезания всего демо в одну загрузку. Этот подвешенный вопрос, который прояснится только после завершения всех сцен, отодвигает написание музыки в самый конец очереди задач.

Составил список проблем с торможением и глюками в NTSC. Исправил часть мелких проблем в сцене с вращающимися буками, и заодно изменил чередование столбцов в эффекте убирания экранов на два символа, так смотрится получше.

23.09

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

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

25.09

Работа немного подзастряла, потому что потерян фокус. Есть множество маленьких и больших задач, и пока непонятно, за что хвататься. Разумеется, есть изначальный план по быстрому завершению крупных элементов. Для выхода из затыка использовал традиционный способ: написал список задач, которыми можно было бы заняться в ближайшее время. Нужно устранить торможение вертикального скролла в сцене вращающихся букв, устранить глюк текста на первой надписи в эффекте матрицы, переписать эффект волн на ассемблер (SDL-прототип сделан аж 20 дней назад), сделать прототип эффекта (придуман вчера) для титульного экрана. Как говорится, придерживаться этих планов я, конечно же, не буду — практика показывает, что именно к запланированному часто не лежат руки, но чешутся делать что-то другое.

Впрочем, на этот раз я для разнообразия действительно последовал списку задач. Починил глюк текста, немного ускорил эффект матрицы, ускорил вертикальный скролл — пожертвовав видимостью сечения с лучом, но в этой сцене оно не так заметно, а плавность важнее. Эффект волн разделяется на три части, собственно эффект, а также вход и выход из сцены. Переписал вход и выход на ассемблер, они легко укладываются в 60 FPS, так как в них обновляется совсем небольшая часть экрана.

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

26.09

Обдумываю реализацию основной части эффекта волн. Занятие удурчающее, цифры не обнадёживают. В этом эффекте надо мало того, что перерисовать весь экран столбцами по 5 тайлов в высоту, но ещё и посчитать 240 (40*6) значений с выборкой из таблицы синуса и умножения на 0..39. Проделать всё это со скоростью 60 раз в секунду наивным алгоритмом PET явно не по плечу. Основная идея, на которую я рассчитываю — генерация процедур для каждой возможной высоты заполнения столбца, причём из пяти байт такая процедура будет записывать в видеопамять только от одного до трёх, так как шаг изменения высоты между соседними столбцами в этой сцене не превышает 8 пикселей. Но похоже, что этого всё равно будет недостаточно. Отрисовка сцены столбцами приведёт к неизбежному сечению с лучом, но здесь выбирать не приходится.

Пока что написал генератор процедур вывода столбцов (программа на C/SDL, выдающая ассемблерный исходник для 6502) и код обвязки из вызова для PET. В простейшем тесте это работает быстро, но, согласно подсчётам, в реальном эффекте понадобится аж 160 процедур вывода столбцов и гораздо больше кода обвязки. Так много процедур нужно, потому что есть два вида столбца — с заполнением от верха и от низа, и также есть полустолбцы сверху и снизу экрана, а клиппинг внутри процедур занял был слишком много времени и места. В любом случае проблемы с этим эффектом ожидаются как по скорости, так и по памяти.

27.09

Изначальный план для эффекта волн предполагал использование таблицы синуса 256 байт со значениями 0..39 и таблицы умножения 40*40. Таблица умножения нужна для получения всех вариантов амплитуд, для плавного начала и завершения движения каждой из полос эффекта. Две таблицы нужны для экономии памяти, за счёт потери в скорости на выборке из таблицы умножения. Это даёт 1856 байт на таблицы, но также в эффекте будет порядка трёх килобайт процедур отрисовки столбцов — многовато.

Решил проэкспериментировать с параметрами эффекта в прототипе на предмет возможности их оптимизации. Уменьшение шагов в таблице синуса заметно ухудшает плавность эффекта. При 64 шагах он становится сильно хуже, чем при 256, очень дёрганый. При 128 потеря плавности видна, но смотрится терпимо. Таблица синуса симметрична с разницей в знаке и порядке значений в четвертях, то есть можно хранить только четверть шагов, а остальные получать из первой. Это позволяет уменьшить размер 256-байтной таблицы до 64 байт, а 128-байтной до 32 байт. Экономия незначительная, но она открывает возможность отказаться от таблицы умножения и хранить сразу нужное количество версий таблицы синуса с заранее отмасштабированными значениями. Если уменьшить количество масштабов с 40 до 20, что конечно заметно, но не так важно — это всего лишь пара секунд от длительности всего эффекта — можно уложить таблицы в 64*20=1280 байт или (с более низким качеством эффекта) в 32*20=640 байт, и заодно избавиться от выборки из второй таблицы. Похоже на план.

Неожиданно возникли проблемы на этапе реализации этого оптимального варианта внутри прототипа. Почему-то нарушается ровность синусоиды, получаемой из четверти, на ней заметна лишняя ступенька, а также сбивается центровка полос. То есть присутствуют некие ошибки в достаточно элементарной математике. Разбираюсь.

28.09

Проблема центровки полос была связана с тем, что столбцы имеют высоту в 5 символов, то есть 40 пикселей, и ноль синусоиды находится на 20 пикселях. Но в верхней части экрана выводится 3 нижних символа от верхней полосы, что сдвигает центр на 4 пикселя ниже. Можно устранить проблему, сдвинув нулевую точку на 4 пикселя выше, но это уменьшит максимальную амплитуду с 20 до 16 пикселей, что вредит внешнему виду эффекта. Был найден компромисс — динамически сдвигать центр с 24 до 20 с увеличением амплитуды. То есть при амплитуде около нуля центр находится на 24 пикселях, а при полном размахе в 20 пикселей — на 20. Это создаёт некоторые проблемы с инверсией фазы при использовании четвертной таблицы синуса, но центр для инверсии можно получить, взяв нулевую амплитуду четверти и умножив её на два. Так как я почти не дружу с математикой и тригонометрией, это объяснение звучит сложнее, чем оно есть — по сути это очень наивный подход. Но он решает проблему.

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

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

29.09

Доработал сцену с вращением всех трёх букв PET одновременно — добавил задержку, чтобы они вращались не синхронно, а начинали и заканчивали одна за другой. Так смотрится поинтереснее.

Реализовал всю оставшуюся обвязку эффекта волн на ассемблере 6502 согласно уточнённому при помощи прототипа на SDL оптимальному алгоритму. Сцена полностью работает, и смотрится прикольно, как и было задумано. Но, как я и боялся, она ужасно тормозит, несмотря на максимально развёрнутый код и большое потребление памяти. И похоже, что даже в PAL она работает на три кадра, то есть со скоростью 16 FPS. Придумал небольшую оптимизацию в конверторе столбцов в развёрнутый код, но она ситуацию не изменила.

Эксперименты подтвердили, что значительное торможение происходит в расчёте высоты столбцов — это 240 значений за кадр, и для каждого помимо выборки из таблицы четверти синуса выполняется логика инверсии значений и 16-битное сложение дельты. При таком количестве значений даже банальное прибавление номера столбца, занимающее всего 4 такта, даёт 960 тактов на кадр — около 5% времени кадра.

Так как в таком виде эффект смотрится очень невыгодно, нужна очень крупнокалиберная оптимизация. Возникла идея перейти от развёрнутого кода столбцов вида:

        lda #tile               ;2
        ldy #0*40               ;2
        sta (Z_DST),y           ;6
        lda #tile               ;2
        ldy #1*40               ;2
        sta (Z_DST),y           ;6
        lda #tile               ;2
        ldy #2*40               ;2
        sta (Z_DST),y           ;6
        jmp return              ;3=33t, 24b (max)

К коду вида:

        lda #tile               ;2
        sta SCREEN+0*40,y       ;5
        lda #tile               ;2
        sta SCREEN+0*40,y       ;5
        lda #tile               ;2
        sta SCREEN+0*40,y       ;5
        jmp return              ;3=24t, 18b (max)

Он значительно быстрее и немного компактнее. Проблема в том, что с таким кодом понадобится уже не 120, а 240 процедур. И если в худшем случае первый вариант занимал 2880 байт ОЗУ, то новый будет занимать 4320 байт — гораздо больше (фактически цифры меньше, часть опкодов внутри процедур может отсутствовать). Но и работать новый вариант будет почти на четверть быстрее. Впрочем, это только часть от всего времени эффекта, есть ещё обвязка для вызова процедур, которая должна быть выполнена в виде фактического, не развёрнутого цикла (так как адрес возврат из процедуры происходит по фиксированному jmp), и есть вышеупомянутый медленный код расчёта высоты столбцов в строке.

Со всей же обвязкой вывод каждого из 240 столбцов на экране старым кодом занимает до 73 тактов, то есть обвязка занимает больше времени, чем сам вывод. Новый вариант с обвязкой 64 такта. Однако, возможен компромиссный вариант с применением самомодифицирующегося кода для вызова процедур по JSR. На первый взгляд, он только замедлит работу, так как JSR/RTS — это 12 тактов, а JMP/JMP — примерно вдвое меньше. Но вызов по JSR позволяет развернуть цикл обвязки. И тогда с обвязкой процедура вывода столбца будет занимать до 57 тактов, а также все процедуры уменьшатся в размере на два байта, то есть 240 процедур займёт до 3840 байт.

Хотя такая оптимизация и увеличивает расход памяти, она могла бы спасти эффект. Переделал конвертер и код эффекта, но визуально это практически не помогло, так как эффект синхронизирован по обратному ходу луча. Эксперименты показали, что если теперь уменьшить количество вычисляемых высот столбцов в четыре раза, то есть с 240 до 60, то эффект начинает укладываться в один кадр в PAL. То есть код расчёта высоты столбцов нужно ускорить в четыре раза.

30.09

После ряда экспериментов по возможности оптимизации эффекта ситуация изменилась не сильно, но было сделано несколько больших изменений. Я снова отказался от четвертной таблицы синуса с предварительным умножением в пользу двух таблиц, синуса и умножения, но уменьшил диапазон умножений до 20. Код для выборки из четвертей занимает слишком много времени, выборка из таблицы умножения быстрее. Внедрил расчёт высоты только для чётных столбцов, с линейной интерполяцией для нечётных, что значительно ускоряет вычисления и не сильно ухудшает внешний вид эффекта. Интерполяция производится в развёрнутом цикле рендера, так немного эффективнее. Также буфер синусоиды (теперь он имеет размер 21 байт) был перемещён в zeropage и кое-где был применён самомодифицирующийся код, чтобы выиграть ещё несколько тактов.

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

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

03.10

Отвлекался от проекта на выходные. Тем временем возникли некоторые идеи по оптимизации эффекта волн. Однако, более эффективная оптимизация, не требующая переделок, была найдена методом тыка — оказалось, что можно отказаться от учёта младшего байта в дельте указателя внутри цикла расчёта фрагмента синусоиды, и таким образом делать 8-битное сложение вместо 16-битного. Впрочем, этого всё равно не хватает для 60 FPS в NTSC, но без ожидания обратного хода луча выглядит довольно плавно.

Провёл эксперименты с экраном логотипа в прототипе на C, чтобы оценить, работают ли задумки. Результат пока сомнительный.

06.10

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

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

07.10

Отладил эффект таймлайна в прототипе на SDL, задал анимацию таймлайна. В работу пошёл первый же вариант, так как времени на раздумывание и переделки при отсутствии критерия для определения улучшения всё равно нет. Переписал сцену на ассемблер, работает без тормозов в 60 FPS. Тормозить потенциально мог бы скролл, без развёрнутых циклов он даже в 50 FPS имел шанс не уложиться в кадр при неудачном размещении опкодов цикла (всего один лишний такт внутри цикла меняет дело). Получившаяся сцена в принципе смотрится неплохо. Плюс ещё одна зелёная строчка в списке сцен и 20 секунд к продолжительности демо. Количество готовых сцен превысило 50%, готовность же демо в целом от этой цифры пока далека.

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

Согласно списку из двух пунктов, первым делом занялся вопросом сборки. Пакет CC65 обладает огромной гибкостью, но такие простые вещи, как сборка фрагментов кода в отдельные бинарники в один и тот же адрес в нём настраивается нетривиально, а документация обширна из запутана. Так как тратить много времени на решение такой тривиальной задачи было бы неразумно, решил собирать сцены в отдельные файлы очень корявым, но рабочим методом — для каждой из сцен делается дополнительный main.s с инклюдом общего кода и кода сцены. Собрал таким образом имеющиеся сцены и оценил масштабы катастрофы. Готовые шесть сцен занимают 30125 байт, что, конечно, никак не влезет в память PET 4032. Но изначально расчёт сделан на упаковку, и сжатые по отдельности при помощи apultra сцены занимают 14210 байт, а несжатый размер самой большой из сцен — с вращающимися буквами — 7616 байт. Пока всё помещается, и даёт надежды на успешное влезание оставшихся сцен в память. Хотя, конечно, хорошо бы немного подуменьшить самые большие сцены.

08.10

Запуск упакованных сцен сходу не заработал. Похоже, что в распаковщик aplib всё-таки закралась ошибка. Чтобы не тратить время на её поиски, я решил попробовать альтернативный пакер, и быстрый поиск в Google выдал мне ZX02, свежую разработку этого года. Он даже немного эффективнее, что позволило сэкономить 374 байта на сжатых сценах, довольно быстрый, и главное — он работает.

По непонятной причине в сцене с вращающимися буквами после распаковки почему-то не работал эффект шума на картинках, хотя в версии обычной всё в порядке. После пересборок он заработал, возможно сначала что-то сползло в адресах общего для всех частей кода. В целом же система сборки заработала, но в паре сцен также нашлись проблемы при возврате из них. В сцене матрицы в одном месте была проблема в выделении byte вместо word под переменную, что портило последующий код. В сцене морфинга был потерян pla в цикле, из-за чего портился стек.

Реализовал в прототипе целочисленный вариант бампа с 16 градациями яркости. В 60 FPS смотрелось бы не так уж плохо. Сделал наивную реализацию для 6502, работает, но очень тормозит. Пока непонятно, как избавиться от нескольких моментов в вычислениях и добиться хотя бы 20 FPS.

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

09.10

Так как времени на грамотное улучшение и оптимизацию алгоритма бампа нет, решил применить принципиальное ограничение — пятно света будет двигаться только по вертикали. Стилистически этого вполне достаточно для той роли, которую должна играть эта сцена, и это позволило существенно ускорить код. Без ожидания обратного хода луча скорость работы даже вполне неплохая. Хотелось бы немного получше, но пока можно оставить в таком виде. В целом план добиваться 60 или хотя бы 30 FPS в сценах благополучно провалился — уже в двух сценах приходится рассчитывать на компромиссное решение.

Карта высот для сцены. Да, просто монохромная картинка.
Карта высот для сцены. Да, просто монохромная картинка.

Сделал обвязку сцены, можно считать её условно готовой для включения в демо. Итого 7 из 11 сцен есть. Но всё ещё не готовы две важные части, без которых не получается полноценного демо — титульный экран и приветствия.

Выполнил рутинную часть по подготовке пустых шаблонов исходников оставшихся сцен, добавлению их в сборочный процесс, incbin'ы в main, и так далее.

Поэкспериментировал в SDL прототипе со сценой лабиринта. Не всё получается так, как представлялось, но более-менее понятно, что в ней делать. Прототип пока требует доработки.

Также поэкспериментировал со сценой титульного экрана. Придумал для него эффект плавно скроллящихся полосок кубиков, чтобы разнообразить не очень интересный эффект появления надписи PET, и в целом направление работы по этой сцене также прояснилось — что нужно сделать, чтобы она смотрелось более-менее богато и интересно при довольно простых эффектах в ней. Реализовал задуманное в прототипе, теперь надо доработать табличку рандома и переписать сцену на ассемблер.

10.10

Завершённый прототип титульного экрана сам собой подсказал идею для ещё одной короткой сценки сразу после него, для усиления сюжетной линии демо: любой вариант тоннеля, неважно какой по способу реализации, который будет символизировать собственно отправку 'назад к PET'.

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

Занялся доделкой титульного экрана. Сгенерировал таблицу рандома для появления надписи PET довольно интересным способом: создал в GIMP градиент от чёрного до белого в 256 пикселей шириной, преобразовал в два цвета с дитерингом по Флойду, результат ещё немного дорисовал вручную так, чтобы плотность включения точек постепенно нарастала, сохранил в 256-цветный BMP, и с помощью HxD скопировал 256 байт растра в табличку рандома.

После завершения прототипа титульного экрана переписал его на ассемблер. Это было довольно утомительно, пришлось поломать голову над появлением надписи PET, которое не укладывалось в 60 FPS, и в целом при объективно простом коде потребовалось довольно много отладки. Осталась небольшая визуальная странность, не совпадающая с прототипом, но на данный момент я решил оставить всё как есть и считать сцену завершённой. 1317 байт в сжатом виде, память ещё есть, но в ней становится всё теснее. Остаётся ещё три сцены и пара килобайт памяти. Есть вероятность, что придётся поломать голову над оптимизацией по размеру или над дозагрузками.

Для проходной сцены с шахматной сеткой, исполняющей функцию разогрева, уже некоторое время назад возникла идея дополнения, делающего её более сюжетно содержательной, и сейчас, когда толком нет времени и памяти, я утвердился в мысли его реализовать. Это не должно занять слишком много времени, так как по сути это простая и хорошо пакующаяся анимация. Идея заключается в том, что в конце эффекта шахматной сетки происходит зум в клетки до тех пор, пока они не станут очень крупными, небольшой скролл, и на появившихся клетках стоят крупные фигуры пешки и ферзя. Появляется надпись MY MOVE CREEP (переиначенная отсылка к Робокопу) и пешка сдвигает ферзя. Пешка символизирует PET, ферзь — другие мощные платформы.

Ещё в самом начале работы над демо была задумана сценка с формой звуковой волны, соответствующей тому, что в данный момент играет CB2 (разные битпаттерны на разных частотах) — в духе волноформовых анализаторов чиптюн-музыки на YouTube. Позже я также решил, что в ней будут показываться greets. Однако, я долгое время не решался приступить к реализации, так как был непонятен алгоритм отрисовки волны без использования огромной кучи заранее просчитанных таблиц, и при этом эффект должен быть максимально быстрым, в 60 FPS. Думаю, это самый замороченный по устройству эффект во всём демо. И вот, видимо вследствие набранного на работе над предыдущими сценами опыта, решение само по себе начало вырисовываться в голове. Написал рабочий прототип эффекта, с алгоритмом без прекалка.

11.10

Сделал анимацию зума для шахматной сценки и сразу же написал код для 6502, без прототипа. Сначала нарисовал её вручную в Gale строго по сетке 8x8 (используется только пробел и закрашенный тайл), а потом посмотрел логику изменения и составил табличку по 4 байта на кадр — цвет начальной клетки, размер клетки и начальное смещение в первой клетке. Таким образом 12 кадров эффекта зума укладываются в 48 байт описания. Правда, развёрнутые циклы вывода (для избежания сечения с лучом) занимают значительно больше. Эффект способен работать на 60 кадрах в секунду, но для того, чтобы зритель мог успеть обратить на него внимание, анимацию придётся замедлить.

Переписал эффект формы волны на ассемблер, попутно нашёл и исправил ошибку в алгоритме. Эффект укладывается в 60 FPS при высоте 15 строк, но несмотря на плавность, изначально он выглядел довольно непрезентабельно — слишком статично. Добавил плавное падение амплитуды до нуля в паузах, подмешал синусоиду к значению ширины — так стало подинамичнее, смотрится поинтереснее.

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

12.10

Доделал шахматную сценку с фигурами: сделал элемент с печатью надписи MY MOVE CREEP, стирание экрана сцены по знакоместам в случайном порядке, вывод спрайта с маской. Сценка задумывалась как очень простая добавочная вставка, но по факту её реализация заняла много времени и сил, и прилично памяти — 1274 байт в сжатом виде.

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

Таким образом, ещё одна сцена готова. Осталось сделать ещё две, чуть ли не самые простые по коду, но дело с ними застряло на уровне прототипа надолго — до сих пор непонятно, как именно они должны выглядеть. Также нужна музыка и сборка. Тем временем, до пати осталась ровно неделя.

13.10

Оптимизировал код сцены с приветствиями по размеру за счёт отказа от некоторых развёрнутых циклов — скорости для 60 FPS хватает и без них. Это позволило уменьшить размер упакованного блока с 2586 до 1517 байт, что очень кстати в условиях стремительно заканчивающейся памяти. Также немного оптимизировал по размеру сцену с вращающимися буквами — самую большую по распакованному размеру. В сжатом виде она стала меньше всего на 4 байта, но на 404 байта в обычном. Это важно, так как в ОЗУ надо оставить свободной область именно такого размера.

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

14.10

Сосредоточился на доделке прототипа сцены с лабиринтом и привёл её в более-менее приемлемый вид, без дополнительных усложнений. Я изначально думал сделать ещё две градации стен лабиринта — 3 на 3 тайла и 4 на 4 без заливки, но это разогревающая сцена, и затягивать её не стоит, а нужное впечатление она создаёт и без этих дополнительных деталей. Возможно, я всё же сделал бы их, если бы сроки не поджимали.

Начал заниматься реализацией сцены с лабиринтом на ассемблере.

15.10

Решил добавить ещё одну капельку контента для повышения градуса содержательности — малипусенькую сценку из списка изначально задуманных, банальные псевдографические помехи с постепенным изменением плотности. Место им найдётся перед сбросом или стоп-экраном — пока не решил, как лучше закончить демо. Объём сжатого кода 180 байт.

По сути в том или ином виде в окончательный сценарий демо попали все сцены из предварительного списка, кроме одной, наиболее интересной — зума в псевдофрактальные псевдографические горы. Он придуман только как идея, но никаких экспериментов реализации не было сделано. Некоторые достаточно банальные идеи объединились вместе в существующих сценах — например, классический тоннель и поле в перспективе стали варпом из полосок после названия, а сцена с каким-нибудь параллаксом, видимо, будет реализована в сцене шахматного поля.

Уменьшил высоту сцены шахматного поля и сценки с фигурами до 24-х строк, чтобы скрыть неудобную половинчатую строчку внизу, делающую эффект несимметричным. Написал развёрнутые циклы вывода горизонтального и вертикального варианта сетки. Для вертикального неизбежно сечение с лучом, но на клетчатом поле оно не очень заметно. Интересно, что в эмуляторе VICE сечение видно, а в MAME нет.

Доделал реализацию сцены лабиринта на ассемблере. В упакованном виде её блок кода занял 698 байт.

16.10

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

Для успокоения творческого зуда набросал-таки приблизительный вариант эффекта с горами в SDL-прототипе. Смотрится плохо, неизвестно, можно ли сделать лучше и сколько времени это займёт, так что эта идея с чистой совестью остаётся нереализованной в списке возможных эффектов.

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

Сразу же занялся вопросом предварительной сборки. При первой попытке, без нахлёста буфера распаковки, не хватило около двух килобайт для нормальной работы сцен — надеюсь, что это не станет слишком большой проблемой. Запустил сборку без данных музыки, что сдвинуло код на 4 килобайта, и впервые смог посмотреть частично собранное демо. Оно доходило до сцены приветствий и падало — упакованная сцена приветствий не работала, но с обычной версией всё было нормально. Дело оказалось в вызове подпрограмм плеера музыки, код которого был размещён в отдельном сегменте. После перемещения кода в другой сегмент демо впервые выполнилось от начала до конца. По результату первого запуска возник план, что нужно сделать для утрамбовывания демо в память, а также несколько идей для небольших улучшений.

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

17.10

В поисках возможностей для оптимизации размера проверил статистику длин RLE-сжатия в анимации вращающихся букв, где на длину отводится всего три бита. Выяснилось, что длина 5 никогда не встречается в используемых данных. Сдвиг на один код позволил сэкономить 119 байт в распакованном виде и жалкие 19 байт в запакованном. Но так как эта оптимизация по сути бесплатная, сделать её было не лишним.

Под небольшое сокращение попала сцена с говорящим компьютером — сэмпл голоса самый очевидный кандидат на уменьшение. Я снизил частоту дискретизации, что не сильно сказалось на и без того низком качестве, но сэкономил около 700 байт на запакованной сцене. Также добавил придуманную вчера шутку, обыгрывающую и невнятное качество голоса, и рефлексию на само подобное демо — сначала слово POSSIBLE в слогане печатается как PASSABLE, потом стирается и заменяется правильным.

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

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

Однако, никаких проблем в коде обнаружить не удалось, зато обнаружилось, что и все последующие сцены также глючат тем или иным образом. Я попробовал поперемещать код сцены без сжатия по всей памяти, в том числе собрать её в нужном адресе, и она работает. Теперь подозрение перешло на компрессор ZX02. Вспомнил, что он использует фиксированную локацию zeropage для своих переменных, а у PET почти вся zeropage забита системными переменными. Но изменение расположения переменных декомпрессора никакого эффекта не дало.

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

В размышлениях, что, помимо депакера, может портить несколько байт, попробовал запретить звуковую систему, и всё заработало. Дальнейшее разбирательство выявило, что дело не в обработчике прерывания, а в вызове плеера музыки. Как оказалось, хотя я предусмотрел в нём защиту от выполнения логики, пока указатель не будет установлен на валидные данные, переменная указателя располагалась в секции DATA, и оставалась неинициализированной — то есть код плеера пытался читать и писать неизвестно куда. Простейшая проблема, на поиск причины которой ушло несколько часов.

Сделал референсный рендер демо в MAME, через экспорт MNG файла и конверсию в AVI посредством AEX's MNG2AVI. В предварительном варианте, где я нажимаю кнопку в моменты синхронизации с музыкой, выходит 3 минуты 50 секунд — довольно неплохая длительность. К сожалению, простая задача перетащить получившийся файл в Reaper для использования в качестве референса обернулась очередным геморроем. MAME выдаёт странное разрешение 399 на 332, которые не принимают кодеки типа x264, а Reaper'у не нравятся loseless-кодеки типа Lagarith Loseless, поэтому пришлось пропустить видео через VirtualDub, кропнуть и перекодировать, и только тогда импортировать в Reaper. Создал и разметил по сценам проект для музыки, успеть написать которую нужно за три дня.

Далее провёл ряд полирующих действий, приводящих демо к более завершённому виду:

  • Немного оптимизировал эффект появления надписи PET на титульном экране по скорости. Это не изменило объём упакованного блока сцены.

  • Заметил, что в сцене приветствий эффект всё же работает на 30 FPS. Так как неизвестно, удастся ли его ускорить, пока сделал отрисовку волны на 30 FPS, а звёзды и приветствия на 60 FPS. Также удлинил нарастание и спадание плотности полоски и добавил паузу, чтобы можно было успеть комфортно прочитать надпись в начале и конце.

  • Вместо сброса, остановки, или выхода в Бейсик (который невозможен при распаковке нахлёстом) решил закончить демо зацикливанием на финальной сцене с шумом, в связи с чем полностью её переделал, добавив эффект смутно проявляющейся в шуме надписи END. Смотрится гораздо интереснее, добавило 251 байт к сжатому блоку сцены.

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

18.10

Начал писать музыку. Пока идёт крайне туго и заставляет сомневаться в возможности успеть закончить демо в срок. Хотя ранее в этом году я делал даже более длинный трек для демо Area 5150, и уложился в аналогичные сроки, так что чисто технически я на это способен. Подобрал темп (240 BPM), наметил части, набросал заготовки музыкальных цитат, нарезал видео под такты музыки.

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

19.10

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

Переорганизовал каналы в Reaper'е более удобным образом. Сделал первый пробный экспорт трека, который на начало суток представлял собой простейшую рыбу из темпа и баса, и впал в уныние — он не конвертируется вторым методом, слишком много уникальных сочетаний питча и длительности, а первый метод выдаёт почти 9 килобайт, при том, что собственно мелодии в треке пока отсутствуют. План заранее синхронизировать демо с рыбой пока проваливается, так как в таком размере трек в память не влезет, а если заняться решением этой проблемы, не останется времени на музыку. Способы решения этой проблемы есть, но время сильно поджимает.

В связи с недостатком времени саундтрек сочиняется по принципу 'абы как', любая идея годится, даже если это очередное банальное ковыряние в Ля миноре. Тем не менее, пока более-менее пригодно к использованию только 40 секунд из необходимых 220.

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

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

20.10

На начало суток была условно-готова ровно половина трека и рыба без мелодий (с импровизационными набросками в записи с MIDI-клавиатуры) для второй половины. Кое-как впихал в титульном экране отсылку на Назад в Будущее — она никак не хотела ложиться в контекст мелодии. Вышло далеко не так удачно, как с Doom в Area 5150. Зато научился играть эту мелодию на гитаре.

Нарочно не придумаешь, но в самый неподходящий момент возникла неожиданная проблема. Во время работы заметил, что у компьютера не прекращает гудеть вентилятор, а любые сайты открываются очень долго. Попробовал запустить AVZ, и она сразу же закрывается. Попробовал установить Касперский, и его установка оказалась заблокирована. Стало ясно, что умудрился очень своевременно поймать вирус — такого не случалось уже много лет. Вчера таких симптомов не было, а сегодня ставил только XPadder, видимо файл был заражён. Пришлось потратить пару часов на решение проблемы путём сканирования системного раздела с ESET SysRescue Live CD, так как было непонятно, какого рода вирус и чем он грозит, да и торможение системы мешало работать. Оказалось, что это некий криптомайнер.

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

Заглянул в расписание событий, чтобы прикинуть, каковы шансы всё же успеть на конкурс. Компо начнётся вечером в субботу, таким образом есть около двух дней помимо сегодняшнего на решение двух сложных проблем: впихуемости невпихуемого (слишком большого по размеру трека) и допиливания таймингов демо. Но два дня — это без гарантий, впритык к показу. Нужно попытаться подготовить хоть какой-то результат хотя бы до утра субботы и далее держать руку на пульсе.

Исходный поток музыкальных данных имеет размер около 17 килобайт и жмётся ZX02 в 3.6 килобайта. При этом в демо зарезервировано около 4 килобайт под данные музыки, и возможно получится добавить ещё около двух. То есть возможен вариант с распаковкой фрагментов трека между сценами в буфер воспроизведения, но в текущем виде получается около 10 фрагментов, это много и неудобно.

Переписал упаковщик потока, который составляет табличку питчей (два метода: PM1 — питч и форма волны в табличке, длительность кадра хранится в потоке; PM2 — в табличке также хранится и длительность), таким образом, чтобы он заменял редко встречающиеся питчи ближайшими похожими, это позволило утрамбовать трек в 10 килобайт. Это дало оценку, какой размер получится, если дотрамбовать до PM2: 6.6 килобайт. Столько всё равно не влезет, что было проверено простой вставкой пустого блока такого размера вместо данных музыки в демо. Эксперимент показал, что без дальнейшей оптимизации сцен на музыку есть только только 5.5 килобайта. Похоже, что в любом случае придётся нарезать на фрагменты и паковать по отдельности, либо же делать дозагрузки. Также есть вариант изобрести какой-то способ потоковой упаковки, не требующий буфера под распаковку.

Провёл тесты по эффективности упаковщиков в надежде выиграть лишнюю пару сотен байт. Результаты в байтах на все 15 упакованных сцен:

Exomizer        20295
ZX0             20640
ZX02            20679
ApUltra         21159

В текущем варианте демо используется ZX02, и это оптимальное решение — дело в том, что у Exomizer и ZX0 распаковщики больше, и весь выигрыш от их применения сводится на нет.

Нашёл интересный компрессор — Huffmunch, который пакует Хаффманом и позволяет распаковывать поток байт за байтом, примерно за 260 тактов за байт, без необходимости в буфере для распакованных данных. Но код распаковщика занимает 330 байт, а данные музыки упаковываются либо из 17 килобайт исходного потока в 6.5 килобайт, либо из 10 килобайт сжатого PM1 потока в 5.5 килобайт плюс 256 байт таблички. То есть опять нужно 6.2-6.5 килобайта, а есть только 5.5, нужно где-то изыскать целый килобайт или более. Запомнил как возможную опцию.

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

Решил попробовать вариант с паковкой музыки, предварительно сжатой PM1, в ZX02 для распаковки в буфер по кускам, в небольших паузах между сценами. Реализовал в PETCB2 экспорт трека по частям — ещё один маркер, закрывающий текущий файл экспорта и открывающий новый с тем же именем и порядковым номером блока в нём. Порезал трек на семь частей, упаковал в PM1, потом упаковал блоки в ZX02. Размер наибольшего куска в PM1 — 2082 байта, размер всех кусков в ZX02 — 3106 байт. При этом первый блок размером 421 байт (2064 байта в PM1) нужно распаковать в буфер ещё до первой сцены, что сдвигает зазор распаковки на сжатый размер блока. По цифрам вроде всё сходится, в таком виде должно влезть, но получится ли это на практике, и насколько плохо будет звучать (с паузами на распаковку, трек изначально хоть и с разбивками, но без потери темпа) — можно узнать только экспериментальным путём. По крайней мере, процесс синхронизации музыки с действиями при таком подходе должен немного упроститься, синхронизировать действие надо в пределах блока.

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

21.10

Вчера дело остановилось на том, что распределённая по памяти и распаковываемая из ZX02 музыка звучит глючно. Проверил экспортированный фрагмент в плеере обычного потока, который выходит из PETCB2 — звучит нормально. Вероятно проблема в плеере или оптимизаторе редких питчей.

Изучение вопроса показало две проблемы: оптимизатор уникальных питчей глючит, портит звук, и в самом плеере есть непонятная проблема, из-за которой он не работает при расположении музыкальных данных ниже определённого адреса ($064a звука нет, $0e4a звук есть). Вторая проблема не так важна, чтобы тратить на неё время, а первая увеличивает объём данных до печальных цифр: 4.5 килобайт на все упакованные ZX02 блоки и 2.2 килобайта буфер распаковки. То есть 6.7 килобайт, либо 5.9 килобайт без учёта размера первого упакованного блока. В таком виде цифры снова не бьются.

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

Однако, повезло: в процессе экспериментов обнаружил, что теперь, когда трек разделён на короткие фрагменты, к ним снова возможно применить метод PM2 без оптимизации питчей. Правда, цифры всё равно не сходились: все архивы 4776 байт, без первого 3982 байта, размер буфера 1638 байт, то есть нужно 5.6 килобайт. Дополнительное искривление извилин родило ещё одну оптимизацию, 4-байтная таблица для PM2 была переработана в 3-байтную, что дало другие цифры: все архивы 4667 байта, без первого 3885 байт, размер буфера 1476 байт. Это уже 5.3 килобайта, цифры снова начали биться.

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

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

Теперь главная проблема: синхронизация действия. Также я заметил, что в сцене с приветствиями подглючивает отрисовка формы волны, но не настолько критично, чтобы нельзя было оставить как есть для party version (не рисуется левый фронт периода). Оставлю этот вопрос до момента, когда надо будет синхронизировать музыку в этой сцене — сейчас эта сцена почти вдвое короче, чем музыкальный фрагмент для неё. Тем временем, осталось шесть с половиной часов до открытия фестиваля.

Взялся за синхронизацию. В отличие от большей части разработки, где тесты велись в WinVICE за исключением критичных к скорости участков кода, синхронизация делается сразу в MAME, чтобы видеть правильную скорость (с возможным торможением) и слышать правильный звук, хотя каждый тест и требует ручного набора команды RUN для запуска, причём не в любой момент, а после паузы в несколько секунд, пока PRG-файл загружается в память эмулятора каким-то фоновым процессом.

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

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

Во избежание глюков экспорта после добавления серии маркеров в трек приходится экспортировать его от начала до конца, что занимает столько времени, сколько он звучит (убираю звук и смотрю YouTube). Есть не очень приятный момент с тем, что после каждого экспорта размер данных слегка меняется, так как внутреннее время секвенсера привязано к эмулируемым кадрам в 1/60 секунды. Из-за этого демо иногда не работает, так как памяти впритык — пришлось на время синхронизации исключить из сборки блок сцены с говорящим компьютером. Это всё решаемо, но на это нужно время, а пока в приоритете довести демо до пригодного к показу состояния.

Далее засинхронизировал эффект матрицы и таймлайн. В матрице пришлось подкрутить длительности падения символов и подобрать начальное псевдослучайное число, чтобы на экране не оставались нестёртые буквы надписей. Титульный экран засинхронизировался по сути сам, добавил только маркер завершения эффекта варпа. Половина длительности демо на этом этапе была засинхронизирована.

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

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

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

Начал пробовать собирать демо обратно в единое целое. Опять не влезает в память, есть мелкие проблемы по синхронизации в разных местах. Видимо часть из них так и останется, для лучшей синхронизации нужно больше времени и сил, и возможно больше изменений по музыке. Вырисовывается перспектива пост-патийной финальной версии.

Фестиваль открывается через десять минут, а я продолжаю сборку. По результатам очередного просмотра предварительной версии внёс ещё несколько правок в синхронизацию и в саму музыку. В том числе замедлил зум шахматной доски, так как из-за привязки к тактам музыки там образовалась длительная пауза.

Потратил около часа на попытки оптимизировать встречный горизонтальный скролл на титульном экране, он подтормаживает из-за музыки. Удалось немного ускорить код, но полностью проблему это не решило. Откладывается на потом. Поправил тайминги, чтобы звук слайда лучше совпадал с разлётом надписей вверх-вниз.

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

С места событий мне позвонил CHRV, вместе с Shuran33 представляющий меня на пати, их помощью я заручился днём ранее на случай, если работу придётся присылать вплотную к дедлайну, и спросил, под какую платформу будет работа — дему ждут и беспокоятся. Я тоже жду. Очень устал, идёт двенадцатый час работы.

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

Для окончательной сборки решил натурально пошаманить — из-за нестабильного размера экспорта музыки и деления её на части есть возможность подобрать куски наименьшего размера из нескольких разных экспортов одного и того же трека. Учитывая, что счёт идёт на байты, сотня байт выигрыша не будет лишней. Собственно, после нескольких итераций такого шаманства, для сборки со всеми сценами не хватило ещё около 150 байт. Безусловно, столько и даже больше можно найти, но на данный момент стоит задача найти их максимально быстро. Поэтому решил пойти на довольно радикальные меры и ещё раз урезать качество голоса в сцене с говорящим компьютером, до совсем уже неприличного уровня, даже с запасом в сотню байт. В пост-патийной версии можно немного улучшить обратно.

Сделал очередной прогон, вроде как завершающий. Всё сработало, всё смотрится более-менее, но забыл про ожидание маркера синхронизации в новоотрезанном заключительном кусочке музыки. Поправил, сделал ещё один завершающий прогон, сразу же с рендером — вдруг прокатит нашару, и не понадобится делать ещё раз. Вроде прокатило!

Возникла новая проблема: как отрендерить видео для показа. Собственно процесс рендера был уже отработан — из MAME в MNG+WAV, из MNG+WAV через MNG2AVI в AVI, далее в VirtualDub. Однако, MAME выдаёт очень странную по пропорциям и размеру картинку, с большим бордюром и нечётными размерами. На скорую руку подобрал параметры ресайза во что-то более-менее удобоваримое. Получился такой процесс: сначала из MNG 399 на 332 в AVI с кодеком Lagarith Loseless, потом в VirtualDub кроп в 384 на 270, потом апскейл до 1536 на 1080 и леттербокс до 1920 на 1080, и уже потом энкодинг в x264. Жуть.

Следующая проблема — синхронизация звука с видео. В эмуляторе она достаточно точная, нет оснований ему не доверять, а вот в записи после перекодировок рассинхрон до секунды, что сильно портит впечатление. Подбор параметров потребовал несколько рендеров, к счастью, они идут примерно две минуты, и в видео есть места, по которым легко проверить попадание звука в видеоряд. Помогла банальная задержка звука в 500 миллисекунд.

Собрал паки — маленький без эмулятора для загрузки через events, большой с эмулятором (сборка MAME весит 600+ мегабайт) через приватное хранилище, залил копию видео на YouTube с приватным доступом, и в 15:00 (случайно совпало) отправил работу. До показа больше суток, но сил, да и свободного времени, на допиливание уже не осталось — всё равно до идеала в таком темпе не довести.

Надо ещё записать ожидания перед компо. Конечно хотелось бы победить, но объективно — платформа очень редкая, отечественному зрителю малопонятная, демо же очень олдскульное и без сложных эффектов, и третий раз в ту же реку с той же формулой наверное уже не войти. Я практически не сомневаюсь, что LowEnd (Combined) будет объединён с чем-то, скорее всего с ZX Spectrum'ом, так как едва ли будет ещё две работы для нетрадиционных для наших краёв платформ. Победа возможна при неявке соперников, если не будет сильных работ, а так, если компо объединят. а спектрумисты поднажали и выкатят что-то на уровне прошлых лет, конечно первые места уйдут им. Тогда может быть попаду в тройку или чуть ниже. Посмотрим.

По поводу мыслей о будущих работах — хотел бы я проделать что-то подобное снова? В данный момент хочется послать такие затеи куда подальше, очень уж много времени и сил на это уходит, too old for this shit и всё такое. Но через некоторое время это настроение, конечно, может измениться.

22.10

Заглянул на events — там показывается количество уже принятых работ — и увидел цифры, обнуляющие мои вчерашние предположения. В LowEnd Combined уже есть три работы, а вот в ZX Spectrum Demo пока всего одна. Так как я не один такой, кто допиливает работу до самой последней минуты, и это в принципе универсальная мировая традиция, думаю, к моменту показа работ станет побольше. Ткну пальцем в небо, назову цифру пять. Значит об объединении компо речь уже не идёт, и всё зависит только от того, насколько хорошие работы выставят конкуренты. С тремя работами в компо есть неплохой-таки шанс войти в первую тройку. Правда, этим планам угрожает платформа БК, для неё пока тоже представлено одно демо, и там шансы на пополнение категории ещё двумя работами до времени показа, как мне кажется, невелики — есть вероятность, что конкурс БК будет слит с LowEnd Combined.

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

23.10

Начало показа работ традиционно задержалось, и даже перевыполнило традиционный план, но посмотреть трансляцию было очень интересно. Мои прогнозы об объединении несколько компо в одно не оправдались, а вот неявка соперника в каком-то смысла оправдалась — конечно, любые работы уже само по себе круто, и тем более для Вектора — я и сам своего рода векторист (сидел на нём год в 1996). Но масштаб конкурирующих работ в LowEnd Combined был просто не тот, и к сожалению, это та победа, гордиться которой придётся с оговорками: не вырвана в тяжёлой борьбе, а начислена просто по факту явки в спортивной форме. Буду утешаться тем, что это победа над собой и сомнениями в собственных силах.

Пати в целом очень удалось. Масштаб по понятным причинам поменьше, чем в 2019, но всё равно, очень много работ, много интересного. Как минимум, были отличные демы для БК и ZX, одна дема-интра для PC мирового, я бы сказал, уровня, и компьютерные платформы были представлены разнообразно. Жаль, что не хватило сил поучаствовать в олдскул музыке, хотя бы бипером — есть ещё удивительные биты в битовницах. Жаль, что не осилил челлендж 'сделать работу' И 'приехать на пати'. Жаль, что из головы вылетело много имён, которые хотелось бы добавить в приветствия.

Послесловие #1

В завершение — немного технической информации. Демо состоит из отдельных сцен, упакованных в отдельные архивы, и также отдельно упакованы фрагменты саундтрека для соответствующих сцен. Фрагменты расположены в порядке, обратном порядку показа, и распаковываются по мере показа, перезаписывая уже не нужные упакованные данные. Для музыки в младших адресах памяти, до всех архивов, выделен буфер в 1470 байт, по размеру наибольшего распакованного фрагмента музыки. Для сцен выделяется буфер в конце памяти, его размер и расположение трижды сдвигаются для увеличения размера буфера, по размеру самой крупной сцены в группе: сначала это 2544 байта для сцен матрицы и таймлайна, потом 5120 байт для сцен от титульного экрана до шахматной сценки, и далее 7088 байт для оставшихся сцен.

Размер в байтах блоков сцен до и после упаковки (исходные сцены включают все нужные им для работы буфера, реально кода там поменьше), а также их расположение в памяти в порядке распаковки:

                исходный        zx02

music_0         1468            789
e_scroll        289             94
e_matrix        1240            464
e_timeline      2528            1023
music_1         802             603
e_title         5119            1353
e_floor         1320            336
music_2         508             330
e_maze          1656            697
music_3         1297            839
e_checkers      3134            1229
e_checkmate     4664            1270
music_4         1362            783
e_petflip       7079            3753
e_waves         6488            2343
music_5         913             528
e_greets        3464            1655
music_6         1176            618
e_morph         6764            2953
music_7         530             324
e_bump          3330            619
e_voice         4259            2263
e_noise         2417            494

итого           61962           25360

Послесловие #2

Как не очень явно следует из текста, изначально демо было показано в виде записи с эмулятора MAME, за отсутствием у меня доступа к реальному Commodore PET — я ранее видел только один такой компьютер в музее, и то он был моделью 80xx в выключенном состоянии. Даже в таком виде демо понравилось зрителям, и вскоре зарубежные пользователи сделали несколько записей работы демо на своих реальных компьютерах. Я думаю, своей популярности демо в немалой степени обязано одной такой записи, выполненной человеком под ником voidstar (v*) tech — она прикреплена в начале статьи. В ней прекрасно демонстрируется прямо-таки магический эффект длительного послесвечения люминофора старых монохромных ЭЛТ-дисплеев.

Другой момент, о котором хочется упомянуть — через время после релиза ко мне неоднократно обращались представители различных небольших музеев вычислительной техники с просьбой сделать воспроизведение демо цикличным, чтобы можно было запустить его на весь день по кругу. К сожалению, технически это невозможно, так как упакованные данные каждой предыдущей сцены затираются при распаковке следующей, но был найден компромисс в виде кастомной сборки, которая вызывает системный сброс через некоторое время после появления надписи END. Как я понимаю, на PET, как и на C64, есть возможность организовать автозагрузку программы с дискеты при включении компьютера, и таким образом после сброса демо может загрузиться и запуститься заново автоматически.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 19: ↑19 and ↓0+19
Comments5

Articles