256 байт intro «Springs» для компьютера Vectrex

Решение написать что-нибудь для Chaos Constructions пришло, как водится, довольно внезапно (в первую очередь потому, что до последнего момента не было очевидно, состоится он в этом году или нет). Так что, времени написать что-то большее, чем работу для конкурса Tiny intro (256 байт для любой платформы) уже не оставалось. Это же определило и выбор платформы, так как под Vectrex я уже писал пару лет назад и проще было вспомнить ассемблер 6809, чем изучать следующий.

Чем (среди прочего) мне нравится демосцена, так это тем что, приступая к работе, понятия не имеешь, что в итоге получишь. Среди нескольких идей, что именно написать, конкретно вот этой не было точно. Две были отброшены потому, что изображение на эмуляторе и реальном Vectrex'e слишком уж отличалось — после каждой сборки заливать всё это в эмулятор ПЗУ и перетыкать его в Vectrex чтобы посмотреть, что получилось — нереально.
Третью идею я было начал реализовывать, но уже в процессе увидел, что сделать такое красиво в 256 байт — слишком сложно. Но, в процессе что-то там переглючило и напомнило пружину. Вот эту идею я, в итоге, и развил:



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

Важным признаком хорошей демо является, на мой взгляд, использование преимуществ именно той платформы, под которую пишется работа. Сильные стороны есть почти у всех компьютеров — на одних это аппаратный скроллинг, на других поддержка большого количества спрайтов, на третьих — мощный процессор. В данном случае оказалось возможным нарисовать «витки» пружины не при помощи вычислений координат каждой точки кривой, как это пришлось бы делать на других компьютерах, а аппаратным способом: при нарастающем напряжении на ЦАП канала Y (вертикаль), через короткие равные промежутки времени менять напряжение на ЦАП канала X (горизонталь), в результате чего получалась кривая, похожая на синусоиду. Причём, для каждой последующей «пружины» амплитуда отличалась и, соответственно, они имеют разную ширину («диаметр»).

Очевидно, пружины должны сжиматься, иначе какие это пружины? «Сжатие» обеспечивается изменением напряжения по вертикали. Говоря точнее, изменением разницы между напряжением в точке, где луч начинает отрисовку пружины (внизу) и точкой, где пружина заканчивается (вверху).
Однако, простым увеличением и уменьшением этой разницы здесь не отделаешься — для пружин в такой ситуации естественна разная скорость сжатия взависимости от текущей растянутости. И вот тут уже без вычислений не обойтись… Понятно, что процессор 6809 команды для вычисления синусов не имеет, а табличка синусов для 256 байтной интро — слишком большая роскошь. К счастью, покопавшись в исходниках ПЗУ Vectrex, я обнаружил там по адресу $fc6d табличку, отлично подходящую для такого случая:


        RTRIGS  FCB     0,25,50,74      SINE TABLE- 16 ANGLES/QUADRANT
        FCB     98,121,142,162  VALUES REPRESENT SINE*256
        FCB     181,198,213,226
        FCB     237,245,251,255

        FCB     255,255,251,245
        FCB     237,226,213,198
        FCB     181,162,142,121
        FCB     98,74,50,25

Единственное, что нужно было сделать — привести значения к нужному масштабу, разделив на 4 двумя сдвигами вправо:

                lsra
		lsra

В результате, пружина сжимается довольно естественно, а заодно и поднимается/опускается по тому же закону — с ускорением и замедлением.

Теперь следующая проблема — кому интересна единственная пружина, прыгающая на одном месте? Так ни компо не выиграешь, ни лайков не наберёшь — пружины должны прыгать одна за другой.
С горизонтальным перемещением, допустим, всё просто. Достаточно равномерно менять напряжение в канале X ЦАП и всё произойдёт само — уехав за экран, где произойдёт переполнение (за -127), пружина автоматически появится с другой стороны экрана (+127). Сложнее дело обстоит с синхронностью прыжков — в живой природе пружины никогда не идут в ногу, у них всегда есть некоторая рассинхронизация в прыжках.

Поскольку состояние пружины в любой момент времени полностью определяется выбранным из таблицы синусов значением, логично хранить это значение отдельно для каждой из пружин. Выбираем адрес в ОЗУ, где будем это дело хранить. Но, предварительно, нужно эту табличку проинициализировать начальными значениями. И не любыми, чтобы в шествии был некоторый порядок. Резервируем несколько байт в ПЗУ, а потом копируем в ОЗУ:

springs         equ	$C890    ; index in sine table for each spring
...
                ldu    #springstmp
                ldx    #springs
                lda    #(3*3)
                jsr    Move_Mem_a            ; A - byte count, U - source, X - destination
...
springstmp:
                db    15,  20,  25

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

Табличка синусов небольшая, поэтому если на каждую итерацию стандартного цикла Vectrex «по кадрам» брать следующее значение, пружины будут скакать очень быстро. Поэтому, берём из таблички следующее значение не каждую итерацию (но рисуем-то конечно в каждую, иначе будет мерцать):

                ldb    ,x                 ; загружаем в b индекс в таблице синусов для очередной кривой (по адресу, находящемуся в регистре x)
...
                lda    frames_c
                bita #$03
                bne    skipinc          ; пропуск
                incb                    ; следующий индекс в таблице синусов
skipinc:
                stb    ,x+              ; сохраняем индекс из b обратно, откуда взяли. И заодно увеличиваем адрес в x на единицу

Ещё для красоты добавим на кончик пружины яркую светящуюся точку — перед тем, как выключить луч, слегка задерживаем его там:


                ldb     #$40   				; end dot brightness (20-30 is ok for release)

repeat_dot:     
                decb
                bne     repeat_dot

                clr     <VIA_shift_reg  	; Blank beam in VIA shift register

Внизу рисуем линию «земли» (к слову, из-за того, что рисование прямой происходит через BIOS, приходится задавать максимальный масштаб. В случае с кривой масштаб задавать не надо, т.к. мы её рисуем полностью «вручную»):


                lda     #$ff                    ; максимальный масштаб
                sta     <VIA_t1_cnt_lo

                ldd     #(-60*256+(-54))        ; Y,X. Перемещаем выключенный луч в начальную точку
                jsr     Moveto_d

                ldd     #(0*256+(127))          ; Y,X  Перемещаем включенный луч до конечной
                jsr     Draw_Line_d

Много места также отнимает надпись внизу. Так что, я решил ограничиться сокращённым названием и указанием размера. Впрочем, была и другая причина. Дело в том, что из-за, во многом, аналоговой природы схем Vectrex'a, его конкретным экземплярам присущи всякие искажения. Конкретно в моём это выражается в двух вещах:

1. При выводе строк процедурами BIOS (там с помощью пунктирных векторов имитируется растровые символы) при увеличении длины строки она начинает наклоняться, так что где-то после 10 символа уже трудно что-то разобрать.

2. С уменьшением высоты, пружины начинает перекашивать влево. Это не «так задумано» — это проблема конкретного экземпляра Vectrex'a. Я попросил владельца двух других машин заснять, как выглядит intro у него. Легко заметить, что разница существенная:



Хотя звук в 256 байтных intro встречается довольно редко, если такая возможность есть — грех ей не воспользоваться.
Чем хорош BIOS Vectrex — в нём припасена не только простая подпрограмма для проигрывания примитивных мелодий, но и несколько примеров самих мелодий (по десятку-другому байт на каждую). Для большой демо это конечно нельзя использовать, но для 256 байт intro — отличная «вишенка на торте»:

                ldu     #$fe38
                jsr     Init_Music_chk          ; Initialize the music
...

; внутри цикла, регулярно:
                jsr     Do_Sound

Надо сказать, что в итоге код получился далёким от совершенства, за что мне немного стыдно учитывая, что ассемблер 6809 очень мощный и приятный, так что не получится сослаться на него, как на причину (как это можно было бы сделать в случае, скажем, с моей предыдущей работой под Videopac, у которого был весьма неприятный для программирования Intel 8048). Полагаю, что имея опыт в программировании под 6809, можно было бы достаточно легко уменьшить нынешние 255 байт до 180-200.

Большое количество регистров, в том числе 16-разрядных, очень экономит нервы и байты. При этом, однако, любопытно, что увеличение/умненьшение регистра на единицу есть только для a,b и d. А вот для индексных (и u) подрузамевается, что при нормальном программированиии они увеличиваются на единицу в рамках команд типа lda ,u+
Даже add не работает с этими регистрами. Впрочем, есть выход. Можно увеличивать и уменьшать их на любое количество байт командами вида leau N,u

7 комментариев

avatar
Чем (среди прочего) мне нравится демосцена, так это тем что, приступая к работе, понятия не имеешь, что в итоге получишь.

— офигенно сказано!

Отличная интра, отличная статья! Правда, пружины начинают сжиматься ещё в воздухе, до соприкосновения с поверхностью, как будто предчувствуют и сжимаются от страха :) Но это, конечно, уже детали!

Фрог, спасибо тебе!
  • sq
  • +3
avatar
Ну, если бросить нежёсткую пружину (и представить, что она будет отскакивать вверх) то только до первого касания в воздухе она будет распрямленной. При следующих, она по инерции будет лететь сжатой. Другое дело, что при касании она должна сжиматься ещё больше — вот этого здесь нет, это верно.
avatar
Точнее, неправильно сказал — не по инерции лететь сжатой, а сжиматься по инерции
avatar
разделив на 8 двумя сдвигами вправо

Опечатка?
  • nyuk
  • +2
avatar
Ага. на 4. Поправил.
avatar
Спасибо, очень любопытно.
avatar
Круто!
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.