Дизассемблированный исходник игры SPACE (V1.1) для
Радио-86РК — посимвольно-точная реконструкция оригинального
кассетного образа tape/SPACE.RK в виде аннотированного i8080
ассемблера.
Автор ВЕЛНОВ П.А. из г. Отрадный Куйбышевской области (середина-конец 1980-х). На титульном экране автор оставил свой домашний адрес и просит писать ему бумажные письма — артефакт эпохи.
- Только для образовательных целей.
- Все права на оригинальный код принадлежат автору. Этот репозиторий — не публикация и не редистрибуция игры, а результат разбора уже известного публичного кассетного образа в учебных целях.
- Бинарный образ
tape/SPACE.RKсохранён без изменений; любые правки производятся только надSPACE.asm, после чегоjust ciгарантирует побайтовую идентичность собранного файла оригиналу.
Открыть в эмуляторе РК86 в браузере:
https://rk86.ru/index.html?run=SPACE.RK
Управление по сюжету: «ПУСК РАКЕТЫ — .»; секретная
клавиша 9 запускает бонусную «лестницу» (см. ниже).
Реверс выполнен с помощью навыка
rk86-reversal
для Claude — он описывает трёхпроходный процесс разбора, идиомы
i8080-кода под РК86 и содержит шаблоны (disasm.py, tobin.py,
Justfile) под полную побайтовую round-trip-сборку.
just ci # ассемблировать SPACE.asm и сравнить с tape/SPACE.RK# пустой diff = всё в порядке
just initial # пересобрать SPACE.bin из кассетного образа (редко)
just disasm # перегенерировать SPACE.asm (ВНИМАНИЕ: затрёт правки)
just clean # удалить промежуточные артефактыCI на GitHub Actions (.github/workflows/build.yaml) запускает
just ci на каждый push / PR — пустой diff обязателен.
Зависимости:
bunx asm8080— ассемблер (https://github.com/begoon/asm8)python3— дляdisasm.py/tobin.pyjust,xxd,diff
| Поле | Значение |
|---|---|
org | 0100h |
| Длина | 3392 байта по заголовку, 3394 в файле |
| Конец | заголовок: 0E3F, фактически: 0E41 |
Образ записан с известным дефектом ROM-кодировщика: поле
end_addr в заголовке на 2 байта меньше реального хвоста
полезной нагрузки (между концом данных и трейлером лежат два
лишних нулевых байта). В Justfile это лечится передачей
--trailer-padding 2 ассемблеру и обрезанием последних двух
байт SPACE.asm так, чтобы вычисленный end_addr совпал с
оригинальным 0E3F.
См. также playbook/common-pitfalls.md в rk86-reversal —
случаи N=2 и N=1 встречаются «в дикой природе».
0100h .. 0188h init: заставка → флип-анимация → start_game
0189h .. 018Dh мёртвая зона (часть jmp + 5 байт)
018Eh .. 01AEh flip_table — 16 пар (key,value) + 00h-стоп
01AFh .. 05B3h intro_screen — анимированный сплеш
05B4h .. 05F6h start_game / init_game_screen / fill_row
05F7h .. 0653h hud_panel — рамка игрового поля
0654h .. 0A21h игровая логика (start_round → game_over)
0A22h .. 0A33h переменные (BSS-перезаписываемые)
0A34h .. 0BFAh 7 спрайтов по 65 байт (шаг 0x41)
0BFBh .. 0CAFh msg_a / msg_b — анимация ракеты
0CB0h .. 0D23h msg_c — следующая стадия анимации
0D24h .. 0D6Ch hud_layout — SCORE / SHIPS / ROCKETS / ATTEMPTS
0D6Dh .. 0D78h end_msg — «= E N D =»
0D79h .. 0D93h priz_game — «P R I Z G A M E»
0D94h .. 0E3Fh title_screen — титул с адресом автора
0E40h .. active_sprite — RAM-буфер на 66 байт
(за пределами файла; копируется sprite_N → сюда)
Все переменные 0A22h..0A33h — BSS-style: поле всегда
записывается через sta/shldдо первого чтения, поэтому
байты в файле — мусор от компиляции, а не значения по
умолчанию. Объявлены поимённо в vars блоке для читаемости:
| Адрес | Имя | Назначение |
|---|---|---|
0A22h | rocket_pos | (word) текущий адрес ракеты в видеопамяти |
0A24h | frame_tick | счётчик кадров анимации (порог 1Bh) |
0A25h | rng_seed | (word) состояние LFSR для rng16 |
0A27h | wave_step | индекс волны (0..2), задаёт строку |
0A28h | var_28 | gap, не используется напрямую |
0A29h | cheat_pos | (word) видеоадрес «лестницы» бонуса |
0A2Bh | cheat_flag | 1 = нажата клавиша бонуса (19h) |
0A2Ch | bonus_done | 1 = «лестница» дошла до верха |
0A2Dh | actor_state | (word) аккумулятор внутри proc_actor |
0A2Fh | side_flag | бит rng — выбор лево/право |
0A30h | last_actor_d | предыдущий D в proc_actor |
0A31h | sprite2_flag | 3 в ветке sprite_2, иначе 0 |
0A32h | mode_flag | 0 = обычный режим, 1 = после бонусного приза |
0A33h | attempts_digit | зеркало правой цифры HUD-счётчика ATTEMPTS |
rng16: lhld rng_seed mvi c,10h ; 16 сдвигов@loop:mov a, h dad h ; HL <<= 1 ani 60h ; отводы 14 и 13jpe @skip inx h ; обратная связь по нечётной чётности@skip: dcr cjnz @loopshld rng_seedretКлассический «параллельный LFSR за один проход»: dad h
сдвигает влево, отводы берутся через ani 60h + jpe
(чётность). Семя 0A25h персистится — игра становится
детерминированной, если поведение игрока повторяется.
ROM-функция 0FD27h ожидает B,C начиная с 1;
обёртка 0888Eh инкрементит оба регистра, затем восстанавливает
их через push b / pop b. Это позволяет вызывающему коду
работать в нуль-индексных координатах.
HL=видеоадрес, D=символ, BC=шаг, E=количество. Используется
четырьмя вызовами в init_game_screen для рисования рамки
игрового поля — горизонтальные и вертикальные линии.
Изящный приём для счётчиков на экране: цифры хранятся в ASCII
прямо в видеопамяти. dec_digit_chain декрементирует младшую
цифру; при переходе через '0' - 1 = '/' (0x2F) цифра
заворачивается в '9', а перенос распространяется влево через
dcx h и рекурсивный цикл. inc_digit_chain — зеркальная
операция через '9' + 1 = ':' (0x3A) → '0'.
Вызывается дважды за цикл: один раз с D=03h (быстрая ветка),
один раз с D=значением шага. Сохраняет состояние между
вызовами в actor_state, при изменении D сбрасывается. Внутри:
- если
cheat_flag=1→ шагнуть бонусную «лестницу» (step_cheat_ladder); - иначе опросить клавиатуру; если нажата
19h(клавиша «9») — взвестиcheat_flagи установитьcheat_posна стартовую позициюvideo_memory + 21*78 + 40.
Спрятанная клавиша 9 (код 19h) включает «лестницу»: каждую
итерацию proc_actor курсор-маркер 0Bh поднимается на одну
строку вверх (HL += -78, см. row_up). Если по дороге
встречается RK86-псевдографика (01h..17h) — вызывается
award_bonus: счётчик призов в правом верхнем углу
инкрементится; на пятом призе игра переходит в mode_flag=1
(жёсткий режим — выводится «P R I Z G A M E» и стартовые
цифры SHIPS / ROCKETS принудительно становятся '2').
Анимация «взрыва» — курсор пробегает строку B=14h..0 и обратно,
после чего декрементится 3-цифровой счётчик ATTEMPTS через
dec_digit_chain. Если все три цифры — '0', проваливается в
game_over: печатается «= E N D =», программа возвращается в
wait_intro_screen.
Отрицательные дельты для dad b над видеоуказателем:
| Константа | Имя | Значение | Эффект |
|---|---|---|---|
0FFB2h | row_up | -78 | подняться на одну строку вверх |
0FFCEh | row_up_short | -50 | вариант для side_flag==1 |
Все обращения к ВРАМ переписаны через выражение
video_memory + y*78 + x, где video_memory equ 76D0h и 78 —
стандартный шаг строки РК86. Это превращает магические 7E79h
в читаемое video_memory + 25*78 + 11 без потери ни одного байта.
Семь блоков по 65 байт начиная с 0A34h (шаг 0x41). Циклы
копирования читают 66 байт (cpi 42h), захватывая первый байт
следующего спрайта — это закладка автора, в коде не дефект:
последний байт спрайта при отрисовке игнорируется.
Активный спрайт копируется в active_sprite (0E40h, BSS) и
выводится puts-ом — то есть 00h-байт в спрайте служит
терминатором, а ESC-Y-последовательности задают позиционирование
относительно курсора.
Самый красивый трюк программы. После puts(intro_screen)
рамка заполнена RK86-псевдографикой. Затем:
- Цикл считывает каждый байт из видеопамяти начиная с
video_memory + 3*78 + 8(верх-лево окна 64×25); - Прогоняет через
flip_table— таблицу пар(ключ, значение)с00h-стопом и значением по умолчанию17h; - Записывает результат обратно в ту же ячейку.
Таблица отображает квадрантную графику в её «зеркальное»
представление (вертикальный флип): 01↔16, 02↔15, 03↔14,
04↔13 и т.п. Плюс 17↔20 (заполненный квадрант ↔ пробел),
что и даёт мерцающий «дыхательный» эффект на буквах SPACE.
Цикл прерывается опросом scan_kbd — любая клавиша вызывает
jmp start_game.
ВРАМ-байты 60h..7Fh отрисовываются как кириллические заглавные
буквы (KOI-7Н). Поэтому в исходнике строки выглядят как
прописная латиница, например:
db'g. otradnyj , ul. otradnaq 17-16' ; Г. ОТРАДНЫЙ , УЛ. ОТРАДНАЯ 17-16db'kujby{ewskaq obl.,' ; КУЙБЫШЕВСКАЯ ОБЛ.,db' pi{ite ! ! !' ; ПИШИТЕ ! ! !{ → Ш, } → Щ, ~ → Ч, | → Э и т.д. — стандартное
соответствие KOI-7Н (см. references/video.md в навыке).
Управляющая последовательность 1Bh 59h ROW COL, где ROW и
COL — ASCII-сдвинутые координаты (значение - 0x20).
В дампе строк она выделена в отдельные db-чанки, например:
db1Bh,59h,21h,24h ; ESC-Y row= 1 col= 4В чистых подпрограммах (rng16, proc_actor, fill_row,
step_cheat_ladder, award_bonus, lose_life, game_over,
dec_digit_chain, inc_digit_chain, wait_intro_screen)
внутренние метки переписаны как @name — синтаксис локальных
меток asm8080, область видимости — до следующего глобального
лейбла. Это убрало шум loc_XXXX без влияния на сборку.
Большой монолит start_round → next_rocket → rocket_step
оставлен на глобальных метках: эти три точки
взаимопереходимы (jmp через границу), поэтому единый @-скоуп
для них невозможен.
Это учебный материал. Все права на оригинальную игру SPACE принадлежат её автору ВЕЛНОВУ П.А. Если вы — автор или правообладатель и считаете, что этот разбор нужно изменить или удалить, откройте issue или напишите.


