23 Commits

Author SHA1 Message Date
snark13 cf06896dbd PoP roomtest: окно Char вокруг control() + общая шина ввода — база под ИИ стража
Инфраструктура пункта 2.  В оригинале у соперника НЕТ своего диспетчера:
autocontrol_* (seg002) выставляет те же глобалы control_*, что и ввод
игрока, а дальше исполняется тот же control() (seg005:252).  Значит перед
портом ИИ надо было привести к этому обе половины:

- control_forward/backward/up/down/shift2 перестали быть static в
  pop_ctrl.c — это общая шина синтетического ввода, объявлена в pop_ctrl.h
  вместе с POP_CONTROL_*;
- control() переименован в pop_control() и работает с Char, а не с Kid;
- ввод игрока обёрнут в окно Char (loadkid/user_control/savekid), как в
  play_frame оригинала.

Ловушка по дороге (ввод отвалился целиком, Kid не двигался): макрос
seqtbl_offset_char вёл на kid_set_seq, который пишет прямо в Kid, а
следом savekid затирал Kid копией Char со старой curr_seq.  В оригинале
seqtbl_offset_char работает именно с Char — макрос переведён на
pop_char_set_seq.  kid_set_seq остался для вызовов ВНЕ окна (pop_map).

Добавлен pop_savekid_state (Kid = Char без кадра): control() кадр не
трогает, а cur_frame в этот момент принадлежит тому, кто последним крутил
play_seq.

Проверено в MAME: бег и упор в стену работают как прежде.
Бюджет (комната 3, 125 кадров): 382 584..383 064 против 380 292..381 282,
то есть +2 300 тактов на копии окна.  0.891 кадра, запас 46 936.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:32:15 +03:00
snark13 8bbc6b4d07 PoP roomtest: интерпретатор последовательностей стал общим (Char) — стражи
Пункт 1 плана стражей.  play_seq был прибит к Киду, поэтому страж стоял на
захардкоженном кадре 166.  Теперь как в оригинале: интерпретатор работает
с АКТИВНЫМ персонажем Char, а вокруг стоят loadkid/savekid и
loadshad/saveshad (порт seg006:809..825).

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

Сопутствующее:
- kid_t и pop_char_t слиты в один pop_char_t (pop_char.h): в оригинале
  char_type один на всех, и без этого общий интерпретатор невозможен.
  Kid получил поля room/charid/sword/alive — они и так нужны боёвке;
- load_frame выбирает таблицу кадров по Char.charid (у стража своя,
  frame_tbl_guard с индексом frame + add_frame − 149, seg006:0293);
- cur_frame разведён на два кэша: страж тикает ПОСЛЕ Кида, и без этого
  kid_draw брал бы кадр стража.  savekid/saveshad раскладывают кадр по
  своему персонажу;
- kid_set_seq пишет ИМЕННО Kid (его зовут pop_ctrl/pop_map вне окна Char,
  иначе loadkid затёр бы), для окна Char добавлен pop_char_set_seq —
  порт seqtbl_offset_char;
- в pop_map 9 голых play_seq() заменены на pop_kid_play() (load+play+save);
- страж входит в комнату через seq_77_guard_stand_inactive (seg002:0208),
  а не через прибитый кадр.

Проверено в MAME: Kid бегает и упирается в стену как прежде, в комнате 12
плиты проваливаются со щебнем (правка задела 9 вызовов play_seq в
физике), страж в комнате 3 рисуется в той же позе, но теперь
curr_seq=0x19A9 и charid=2 — кадр получен прокруткой последовательности,
а не константой.

Бюджет (комната 3, 150 кадров): 380 292..381 282 тактов против
370 140..371 160 до правки, то есть +10 150 (+2.7 %).  Основное — не
копии Char, а то, что страж теперь реально крутит интерпретатор каждый
кадр, а раньше стоял замороженным.  0.887 кадра, запас 48 718.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:11:10 +03:00
snark13 565ba98852 PoP docs: бэклог идей — начат с «отключать мышь на время игры»
Мышь игре не нужна (управление — raw-клавиатура, которую мы и так
забираем у DSS), а её прерывания воруют такты из бюджета, занятого на
86 %.  Эффект измерен побочно: при движении мыши на хосте кадры выбивались
до 1.5 кадрового периода, при неподвижной — 225 кадров без превышений.

Записано с тем, что проверить (есть ли в RST 30h выключение, сколько
стоит одно прерывание, восстановление состояния на выходе) и почему не
сейчас: выигрыш только когда игрок двигает мышью, риск оставить систему
без мыши после выхода — заметный.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:58:40 +03:00
snark13 ac9871c58c PoP roomtest: факел анимируется каждый логический кадр (TORCH_ANIM_DIV)
Делитель (torch_tick & 1) занижал скорость пламени вдвое: один логический
кадр = 3 vsync и соответствует игровому тику оригинала, а animate_torch
(seg007:03C1) меняет кадр КАЖДЫЙ тик.  Теперь темп задаётся явной
константой TORCH_ANIM_DIV (1 = как в оригинале, 2 = прежнее поведение),
счётчик компилируется только когда он реально нужен.

Побочный эффект важнее визуального: раньше половина кадров делала работу
факелов, половина нет, и бюджет кадра «прыгал».  Замер по 100 кадрам
до правки: 349 008..371 262, разброс 22 254 такта (6.2 %).  После: по
225 кадрам 370 140..371 160, разброс 1020 тактов (0.27 %) — каждый кадр
стал худшим случаем, и цифре можно верить.

Бюджет сейчас (комната 3, Kid + страж, статика): 0.861..0.863 кадра,
запас 58 840 тактов до 430 000.  Кроссбанковых вызовов 19 за кадр
(~654 такта каждый = 12 400, 3.3 % кадра) — столько максимум вернёт
батчинг; профиль вызовов снят breakpoint'ом на ___sdcc_bcall_ehl с
печатью HL/E и раскладкой адресов по .map.

Замечание по методике: мерить надо ПО МНОЖЕСТВУ кадров.  Единичные
всплески до 1.5 кадра, которые я сперва принял за проблему движка,
оказались наводкой от прерываний мыши на хосте — при неподвижной мыши
225 кадров подряд без единого превышения.

Проверено, что --w3 (он остался в sprinter-cc) кладёт в W3 только код и
rodata: --dataseg ему не передаётся, глобал --w3 модуля лёг в общий
_DATA — сюрпризов при возврате к резиденту не будет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:57:26 +03:00
snark13 1e6c377edc PoP roomtest: pop_bg и pop_map в банки; --dataseg BANKn стал опцией
Резидента --w3 больше нет: отрисовка (pop_bg + pop_gdraw) уехала в БАНК 2,
физика/коллизия (pop_map) — в БАНК 3.  Куча W1/W2 1294 -> 6750 Б.

Что это разблокировало.  Резидент был тупиком: из банка он недостижим ни
прямо, ни транзитивно, поэтому pop_map (самый крупный модуль, 5.8 КБ) в
банк было не увести — он зовёт mob-отрисовку.  Проверено, что банк->банк
РАБОТАЕТ: ___sdcc_bcall_ehl читает страницу окна портом 0xE2 и кладёт её
на СТЕК своего кадра (runtime/bank.s), поэтому вложенность корректна по
построению.  Подтверждено в MAME цепочкой W1 -> банк1 -> банк2 -> банк1:
nested=124 after=8, ровно ожидаемое.  Значит развязка mob'а (самое
рисковое место, loose-полы) НЕ понадобилась — pop_map зовёт pop_bg
трамплином.

Правила вызовов проверены на сгенерированном asm и записаны в memory
sdcc_banked_call_rules: трамплин выбирает ОБЪЯВЛЕНИЕ (__banked), а не
раскладка — даже внутри одного .c между __banked функциями он есть.
Внутрибанковые функции оставлены непомеченными и зовутся напрямую, в т.ч.
через границу файла (pop_gdraw -> pop_fore_over_char).

sprinter-cc: --dataseg BANKn БОЛЬШЕ НЕ ставится по умолчанию.  Раньше вся
писучая память банкового модуля уезжала в страницу банка и снаружи не
читалась (проверено на .map: глобал лёг по 0x0001C000) — грабли на
каждом переносе.  Теперь данные банков по умолчанию в общем _DATA (W1/W2,
замаплен всегда), а прежнее поведение — по явному --bank-data.

Замеры (комната 3, Kid + страж; кадр Sprinter в турбо = 430 000 тактов):
  до переноса          338 508  (0.79 кадра)
  + pop_bg в банк 2    347 100  (+2.5 %)
  + pop_map в банк 3   371 100  (+9.6 % к исходному, 0.86 кадра)
Плата — трамплины (~654 такта на вызов, ~30 вызовов за кадр).  При
пейсинге в 3 кадра это 29 % логического кадра, но запас до ОДНОГО кадра
всего ~59 000 тактов — под звук его надо возвращать (следующий шаг:
батчить кроссбанковые вызовы, начиная с pop_redraw_needed).

Профилирование бордюром включено по умолчанию (make PROF=0 выключает) и
переведено на реально работающие биты: бит 0 (красный) у бордюра Sprinter
ИГНОРИРУЕТСЯ, поэтому различимых состояний четыре и значения обязаны быть
чётными — 0 чёрный (ждём vsync), 2 синий (логика), 4 зелёный (фон),
6 циан (спрайты).  Раньше нечётные номера сливались и полосы не читались.

Проверено в MAME: комнаты 1/3 рисуются как прежде, бег и коллизия
работают, в комнате 12 плиты проваливаются со щебнем — то есть цепочка
loose банк3 -> банк2 живая.  Полосы бордюра в комнате 3: логика 73
строки, фон 64, спрайты 128, свободно 23.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:31:58 +03:00
snark13 0280b05933 libc/kbd: held-карта клавиш в биты (512 -> 64 Б); эталон размеров принят
Разгрузка W1/W2 под будущий ИИ стражей: _kbdraw_down был БАЙТОМ на
скан-код (512 Б в _DATA при 32-килобайтной раскладке).  Теперь бит на
код: код>>3 = байт, код&7 = бит, расширенные (префикс 0xE0) — смещение
+32 байта вместо +256.

Трамплин прерывания строит маску СДВИГОМ, а не таблицей: таблица
потребовала бы `ld hl,#метка` внутри трамплина, а он копируется в W2
побайтно и обязан быть без абсолютных само-ссылок (см. его шапку).
Маска строится в BC, поэтому в клавиатурной ветке добавлен push/pop bc.
Трамплин вырос 244 -> 267 Б, буфер копии поднят 320 -> 336 (запас 69 Б).

Проверено в MAME на roomtest, все три класса клавиш:
  - обычные: '=' (обход комнат) и 'K' (чит-убийство стража — читал
    guardhp_curr/delta: 3/0 -> 0/-3);
  - расширенные (E0): стрелка вправо — Kid добежал до края комнаты;
  - модификаторы: удержание Shift ставит бит 2 байта 2 карты
    (скан-код 0x12), отпускание снимает.

Скорость: кадр 334 716 -> 338 508 тактов (+1.1 %) на битовой арифметике
в kbd_raw_down (~15 вызовов за кадр); при бюджете 430 000 это 0.79
периода вместо 0.78 — регрессии нет.

Итог по roomtest: данные 4422 -> 4022 Б, куча W2 996 -> 1294 Б.

Эталон размеров принят заново (make size-baseline): _CODE десяти
программ вырос на 14-23 Б — это код битовой арифметики в трамплине и
kbd_raw_down, обмен на -448 Б данных, которые size_check не считает.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 00:50:37 +03:00
snark13 f2093e0d89 PoP roomtest: fore-слой — окно клипа, кэш кладки; кадр 2.5 -> 0.78 периода
Жалоба: стойка неподвижных Кида и стража занимала больше полутора
кадровых периодов.  Гипотеза «виноват __banked» ЗАМЕРОМ НЕ
ПОДТВЕРДИЛАСЬ: весь банковый вызов (трамплин + смена страницы + тело
pop_guard_tick + возврат) стоит 654 такта при бюджете кадра 430 000.

Как мерил (выборка PC бесполезна — мост MAME отвечает из фреймового
колбэка, все сэмплы падают в обработчик прерывания): breakpoint'ы MAME с
действием {printf totalcycles; g} на входах фаз главного цикла, разности
соседних меток = стоимость фазы.  Плюс профилирование полосами бордюра
(make PROF=1, макрос PROF() в roomtest.c) для быстрого взгляда.

Замер комнаты 3 (Kid + страж), такты, кадр = 430 000:
  fore поверх стража  431 964
  fore поверх Kid     402 816   -> 78 % всей работы кадра
  остальное           241 956
  ИТОГО             1 076 736   = 2.5 кадра

Две причины, обе устранены:

1. Fore-слой рисовал ЦЕЛЫЕ тайлы, хотя существует ровно для того, чтобы
   вернуть куски поверх спрайта — за его прямоугольником в видеопамяти и
   так правильный фон.  Введено ОКНО клипа (pop_fore_set_clip): спрайт
   сообщает свой итоговый габарит (у Kid — с клинком, брызгами и
   обрезкой clip_char), fore-проход режет по нему.  Отсев трёхступенчатый:
   тайл целиком (tile_in_fclip, до обращения к атласу), кусок по грубому
   габариту (до gfx_w0_map — w/h лежат в EMM-странице), и точный клип в
   blit_b.  Для последнего добавлен libbgi-примитив
   gfx_blit_part_noclip — пара к gfx_blit_noclip, но под-прямоугольник.

2. Оставшиеся 341 К после клипа оказались НЕ пикселями: 18 вызовов
   pop_prandom за проход, ~10 700 тактов каждый (32-битный LCG:
   __mullong ~8 000 + __moduint).  Раскладка кладки тайла — чистая
   функция (комната, ряд, колонка), то есть константа комнаты, а
   wall_pattern пересчитывал её каждый кадр.  Теперь кэшируются готовые
   РЕШЕНИЯ (что рисовать и с каким смещением), 3 байта на тайл, сброс в
   pop_room_draw.  Порядок вызовов prandom воспроизведён один в один,
   включая то, что значение метки берётся только при сработавшем условии.

Итог того же замера: fore поверх стража 37 464, поверх Kid 46 464,
кадр целиком 334 716 = 0.78 периода (было 2.5).  Ускорение 3.2x, сами
fore-проходы — 10x.

Проверка отсутствия регрессии: попиксельная разность скриншотов комнат
1/2/3 до и после — отличаются ТОЛЬКО языки пламени факелов (анимация),
кладка и метки совпадают байт в байт.

Побочно: sprinter-cc научился пробрасывать -DNAME в sdcc.

Память: куча W2 1245 -> 996 Б (кэш кладки 120 Б), резидент W3 12 819 ->
14 512 (свободно 1872 Б — становится тесно), банк 1 236/16384.

ВНИМАНИЕ: make size-check показывает рост 7 программ, но эталон
docs/size_baseline.tsv отстал (последний раз принят в 484b18d, libbgi
менялась в 95c22be/c127a4b/64ce633) — к этой правке рост отношения не
имеет: новый модуль библиотеки в чужие программы не линкуется.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 00:19:32 +03:00
snark13 af5f0a4638 PoP roomtest: отрисовка стража в резидент W3 + fore-окклюзия + off-by-one спрайта
Разгрузка W1/W2 перед ИИ стражей (вариант 2 из двух обсуждённых).

1. pop_guard.c разделён по окнам: состояние/логика (Guard, HP, enter,
   kill, load_frame) остаются в W1/W2 — их обязан видеть банк guards.c;
   ОТРИСОВКА уехала в новый pop_gdraw.c, собираемый как --w3 (резидент).
   Правило границы: резидент = только то, что рисует и зовётся
   исключительно из главного цикла.  Кадр стража стал глобальным
   (pop_gframe): заполняет логика, читает резидент.

2. Страж не окклюдировался передними гранями тайлов — рисовался поверх
   столба.  В оригинале любой Char это запись midtable, а foretable
   рисуется после всех midtable (draw_tile_fore, seg008:690), т.е. столб
   перекрывает всех одинаково.  Футпринт персонажа выделен из
   pop_fore_over_kid в char_footprint(), поверх него добавлен
   pop_fore_over_char() — слой fore + полоса потолка, без оверлеев поз
   виса/полёта/подъёма (у стража их нет; появятся — портируем
   redraw_at_char2 общим кодом, а не догадками).

3. Упаковщик стража: тот же off-by-one, что уже ловили у Kid.
   load_chtab_from_file(id_chtab_5_guard, 750) даёт images[0] = res751,
   а рисование индексирует images[frame.image] — значит image=N это
   res(751+N), а не res(750+N).  Из-за сдвига frame_166_stand_inactive
   рисовался как res767 (выпад) вместо res768 (стойка).

Проверено в MAME: страж в комнатах 3 и 21 стоит в правильной позе;
окклюзия подтверждена патчем Guard.x в живой сессии — при заходе за
столб спрайт корректно срезается его передней гранью.

Память: _CODE 26 149 -> 25 703, куча W2 805 -> 1245 Б, резидент W3
11 656 -> 12 819 (свободно 3565 Б), банк 1 236/16384 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 23:14:21 +03:00
snark13 3dbad6120c PoP roomtest: страж — спрайты, таблица кадров и появление в комнате
Шаги 1-2 из плана стражей:

1. Спрайты (chtab_5_guard, база 750).  Новый упаковщик pop_pack_guard.py:
   data/GUARD/res751..784 -> GUARD\g0..g4.atl (5 EMM-страниц, адресация
   id>>3 / id&7, как у Kid).  Палитра берётся НЕ из PNG, а из res10.bin
   (guard_palettes: 7 палитр по 16 цветов, 6-бит) по level.guards_color —
   на уровне 1 у обоих стражей color = 2; группа слотов 0x90..0x9F
   добавлена в общий kid.pal.

2. Таблица кадров стража у оригинала СВОЯ (frame_tbl_guard, seg006:372,
   41 запись) и индексируется как frame + add_frame - 149, где add_frame
   = 70 для кадров 102..106.  Она дописана в kid_data.bin (3515 -> 3720 Б,
   смещение в KID_BIN_GFRAMES_OFF); pop_kid получил pop_kid_data_frame()
   — чтение кадра из ЛЮБОЙ таблицы страницы данных.

3. Появление: pop_level_guard() читает guards_tile/dir/color/skill из
   уровня, pop_guard_enter() ставит стража по enter_guard (seg002:0112) +
   pos_guards (seg003): row из тайла, y = y_land[row+1], x из колонки,
   charid = guard, sword сложен, alive = -1, HP = 3.  Отрисовка
   pop_guard_draw() — та же математика, что kid_draw (load_frame_to_obj +
   calc_screen_x_coord), но атлас стража и своя таблица кадров; heal по
   странице дабл-буфера, как у Kid.

Интерпретатора последовательностей у стража ПОКА НЕТ: кадр ставится
напрямую (166 = frame_166_stand_inactive, что и даёт seq_77 при входе в
комнату).  play_seq для произвольного персонажа + ИИ — следующая фаза.

Проверено в MAME: в комнате 3 страж появляется на своём месте (ряд 1,
кол 7) и рисуется; цвета совпадают с эталонным рендером спрайта в
палитре color=2.

ВНИМАНИЕ по памяти: куча W2 просела до 805 Б (было 2349).  Перед ИИ
стражей нужен шаг 5 плана (данные: room_modif 720 Б, dl1/dl2 512 Б,
_kbdraw_down 512 Б) либо вынос кода отрисовки стража в резидент W3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:57:38 +03:00
snark13 1dc89b0f26 PoP roomtest: ресурсы по каталогам образа (BG/KID/LEVELS), цель make hdd
Все .atl лежали в корне диска рядом с exe — с атласами стражей корень
зарос бы окончательно.  Теперь ресурсы разложены по каталогам (8.3, как
принято в DSS):
  BG\     фон (env0..4, wall, fore, pot)
  KID\    персонаж (kid0..27, kid.pal, sword, kid_data.bin)
  GUARD\  стражи (появятся здесь)
  LEVELS\ уровни (res2001.bin)

make_hdd.sh принимает аргумент вида КАТАЛОГ:файл — создаёт каталог на
образе и кладёт файл туда; без префикса файл идёт в корень.  В Makefile
roomtest появилась цель `make hdd`, которая собирает образ с этой
раскладкой (раньше команда набиралась руками на 15 строк).

Проверено в MAME: DSS открывает пути вида KID\kid0.atl — комната
рисуется, Kid и факелы на месте, то есть все атласы, палитра, таблицы
анимации и уровень грузятся из подкаталогов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:42:16 +03:00
snark13 e1ee447b7f PoP roomtest: каркас стражей в БАНКЕ + режим читов (K — убить стража)
Раскладка (по подтверждённой пробником модели, tests/w3bankgfx):
- roomtest переведён на MEMORY=huge: та же small-раскладка резидента
  (CODE в W1, DATA за ним) плюс банки кода в W3;
- guards.c собирается как --bank 1=guards.c — там будет ИИ и боёвка;
- СОСТОЯНИЕ стража живёт в W1/W2 (pop_guard.c): писучие статики
  __banked-модуля линкуются в страницу банка и снаружи не читаются, так
  что банк — только код;
- поля pop_char_t повторяют char_type оригинала (types.h:302), чтобы порт
  seg005/seg006 ложился один в один.

Режим читов (порт cheats_enabled, seg000:111): глобальный флаг pop_cheats,
на время разработки включается в main.  Реализован один чит — K (kill
guard, seg000:786): скелета не берёт, живому стражу ставит
guardhp_delta = -guardhp_curr и alive = 0.  Обработка по фронту нажатия.
Остальные читы оригинала не портированы.

Проверено в MAME: приложение в huge-раскладке стартует, комната рисуется,
Kid бегает — то есть банкованный pop_guard_tick() зовётся каждый кадр
через трамплин и корректно возвращается; K не роняет приложение (стража
в комнате пока нет).  Банк занят на 6 Б из 16384.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:31:11 +03:00
snark13 18f5115e0d docs(PoP): план v2 — результат пробника банка (модель подтверждена)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:19:00 +03:00
snark13 b3bf2cca3d tests/w3bankgfx: пробник модели «huge + резидент W3 + банк W3 + графика»
Проверяет то, на чём стоит план раскладки PoP (layout_plan_v2.md §2):

R3  резидент/HOME -> __banked через трамплин работает;
R4  примитивы libbgi можно звать ИЗ БАНКА: _bgi_begin запоминает текущую
    страницу W3 (порт 0xE2), _bgi_end её возвращает — банк переживает
    рисование и продолжает исполняться;
    то же верно для функции W1/W2, вызванной из банка: она рисует, а в W3
    остаётся страница БАНКА, не резидента.

Результат в MAME: все пять полос на месте, вердикт ЗЕЛЁНЫЙ.  Замеры:
страница банка 0xF0 до рисования, после прямого блита и после возврата из
W1/W2-функции — та же 0xF0; резидент 0xF3; банк дожил до конца и вернул
корректное значение.

Два побочных вывода, важных для стражей:
1. Писучие статики __banked-модуля линкуются В СТРАНИЦУ БАНКА (адрес
   0x1C000+), снаружи их не прочитать — состояние банка держать в W1/W2.
2. Инлайновый `in a,(0xE2)` посреди тела функции затирает A, куда SDCC уже
   положил параметр (в первой версии пробника цвет заливки становился
   номером страницы, и «резидент не рисовал»).  Читать порт отдельной
   __naked-функцией.

Имя exe — 8.3 (w3bgfx.exe): DSS длинных имён не понимает.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:18:27 +03:00
snark13 3b2dd8bfcc docs(PoP): план v2 — статус выполнения шагов 1..4 и найденная ловушка SDCC
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:08:47 +03:00
snark13 ecf5ecfc14 PoP roomtest (план v2, шаг 2): общая геометрия в pop_geom
Сведены дубли, разъехавшиеся по модулям:
- x_bump[20] был в pop_kid (uint8_t!) и в pop_map (int16_t) — теперь одна
  таблица int16_t;
- y_land[5] — две копии;
- y_to_row_mod4 — в pop_bg и pop_map;
- 32-битный LCG оригинала (prandom) — в pop_bg и pop_trob; функция теперь
  одна, а СИДЫ остались раздельными (у раскладки кладки и у фаз факелов
  свои последовательности, смешивать нельзя — иначе поедет рисунок стен).

Экономия по коду скромная (_CODE 24462 -> 24421, W3 11643 -> 11632: часть
выигрыша съели межмодульные вызовы).  Главное здесь другое: pop_geom лежит
в W1/W2 и не трогает графику, то есть это тот самый «чистый» слой, который
сможет звать __banked-код стражей (docs/layout_plan_v2.md §4, §5.2).

Проверено в MAME: комната 1 после пересборки отрисована ПОБАЙТОВО так же,
как до правки (0 различающихся пикселей в области комнаты) — значит
последовательности PRNG и геометрия не поехали; Kid бегает.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 21:05:24 +03:00
snark13 4cffbc9aa4 PoP roomtest (план v2, фаза 1b): pop_map переведён на пометки перерисовки
Loose-полы, плита-потолок и щебень от приземления больше не зовут pop_bg —
ставят пометки (pop_set_redraw / pop_set_redraw_above), которые разбирает
pop_redraw_needed из главного цикла.  Удалены самодельные счётчики
loose_bake/loose_rest/ceil_rest/ceil_bake/land_bake: их роль (вторая
страница дабл-буфера) теперь у счётчика страниц в пометке.

Осталось ОДНО исключение: падающий кусок (mob) — spawn/tick/pos.  Это
движущийся ОБЪЕКТ, а не перерисовка тайла, и в оригинале он живёт отдельно
(mobs + draw_moving), поэтому разделение его на логику и отрисовку —
следующая фаза.  Из-за него pop_loose_tick остаётся единственной функцией
pop_map, которую нельзя звать из __banked-кода; вся коллизия, физика,
кромки и предметы — то, что понадобится стражам — чисты от графики.

Замер: _CODE 24718 -> 24462, _DATA 4301 -> 4219 (ушли rest-массивы).

Проверено в MAME: комната 12, осторожный шаг на плиту — тряска, падение
плиты, Kid проваливается на ряд 1, дыра и щебень отрисованы; на
замороженном кадре обе страницы дабл-буфера побайтово совпадают в области
изменений (7 строк).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:44:56 +03:00
snark13 24bb724c22 PoP roomtest (план v2, фаза 1a): пометки перерисовки вместо прямых блитов
Порт архитектуры оригинала: логика анимации тайлов НИЧЕГО не рисует, она
ставит флаг (set_redraw_full/set_wipe/redraw_20h/redraw_21h, seg007), а
отрисовка идёт отдельным проходом redraw_needed (seg008:0178).  У нас
появился pop_redraw.c/.h: pop_set_redraw(tilepos, вид, страницы) +
pop_set_redraw_above(col, ...) + pop_redraw_needed(), который зовёт
главный цикл в слое фона (до kid_draw).

Отличие от оригинала (наша платформа): счётчик пометки — это ЧИСЛО СТРАНИЦ
дабл-буфера (обычно 2), а вид перерисовки хранится явно (heal+поверх или
запечь фон), потому что у нас у каждой страницы своя ОЗУ-копия фона.  В
оригинале вид кодируется тем, в какой из таблиц redraw_frames_* стоит флаг.

pop_trob переведён на пометки: пики, кнопки, дверь уровня.  Его самодельные
массивы spike_rest/button_rest/ldoor_rest/rest_pending удалены — их роль
теперь у счётчика страниц в pop_redraw.  Прямыми вызовами pop_bg осталось
только пламя факела и пузырёк зелья: это не тайловая перерисовка, а
покадровый оверлей; из-за них pop_process_trobs остаётся единственной
функцией модуля, которую нельзя звать из банка.

Зачем: из __banked-кода резидентная страница W3 недостижима транзитивно
(docs/layout_plan_v2.md §2 R2), поэтому логика, которую будут звать стражи,
не должна вызывать pop_bg.

Проверено в MAME: комната 6 — Kid на кнопке-открывалке, решётка в шве
поднимается; сошёл с кнопки — закрывается; упал в шахту на пики — пики
выдвинулись и отрисованы (кадр 177).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:37:03 +03:00
snark13 eef6eebd8c PoP roomtest: фикс чтения таблицы кадров — баг кодогенерации SDCC
Кадры Kid читались из EMM-страницы по НЕВЕРНОМУ адресу для индексов >= 52.
Запись

    (const uint8_t *)(0x100) + (uint16_t)i * 5u

SDCC 4.5 собрал так: умножение честно в 16 битах (add hl,hl / add hl,bc),
а затем `ld c,l` + `inc b` — то есть взял только МЛАДШИЙ байт результата и
подставил старший байт константы.  При i*5 >= 256 адрес уезжал на -256*k,
и cur_frame наполнялся чужой строкой таблицы: у кадров бега/шага/подъёма
пропадал бит FRAME_NEEDS_FLOOR — Kid «вкручивался» в пол и проваливался
вниз, последовательности кадров не соответствовали seqtbl.

Фикс: адрес считается в uint16_t (i*5 = i + i<<2, без умножения) и
кастуется один раз — сгенерированный код теперь сохраняет старший байт
(ex de,hl / inc d).

Коварство бага: тот же паттерн в pop_level.c (room_fg_ptr/room_bg_ptr,
links) компилируется ПРАВИЛЬНО — проверил все три места по .asm.  Записано
в memory sdcc_z80_const_ptr_index_bug.

Проверено в MAME на замороженных кадрах: 10 выборок (кадры 4,10,15,49,50,
54,123) — cur_frame совпадает с таблицей во всех, включая те, что раньше
были испорчены.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:24:03 +03:00
snark13 3de8c7500c PoP roomtest (план v2, шаг 4): в W3-резиденте остаётся только pop_bg
--w3 берёт ОДИН файл на флаг, поэтому запись "--w3 pop_trob.c pop_map.c"
означала "W3 = pop_trob", а pop_map всё это время ехал в W1/W2 (в
build-каталоге лежал осиротевший w3_pop_map.rel).  Теперь список явный.

pop_trob переведён в W1/W2: в W3 должно оставаться только то, что банк
никогда не позовёт (из __banked резидентная страница W3 не видна ни
напрямую, ни транзитивно — docs/layout_plan_v2.md §2 R2).  pop_trob же
стражам понадобится: в оригинале они тоже давят кнопки.

Замер: W3 14376 -> 11643 (свободно 2008 -> 4741 Б), W1/W2 _CODE
21634 -> 24367 (куча 5315 -> 2582 Б).  Освободившееся место в W3 —
задел под шаг 3 (loose/потолок из pop_map, чтобы pop_map стал
bank-safe).

Проверено в MAME скриптом: комната рисуется, Kid бежит и тормозит
(кадры 15 -> 10 -> 15), факелы анимируются (152 различающихся пикселя
между соседними кадрами) — то есть pop_trob работает из нового окна.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:30:57 +03:00
snark13 8e33cd07bc PoP roomtest (план v2, шаг 1): таблицы анимации Kid -> EMM-страница
kid_frames (241*5) и kid_seqtbl (2310) занимали 3.5 КБ в _CODE окна W1/W2
— самого дефицитного ресурса.  Теперь они лежат в kid_data.bin (отдельная
EMM-страница), которая маппится в W0 ровно на время play_seq — один
map/unmap за тик, в фазе тика, без конфликта с атласом в W0.

Ключ к переносу — порт load_frame/cur_frame (seg006): оригинал раз за тик
копирует кадр в структуру, и вся коллизия/отрисовка читает ЕЁ, а не
таблицу.  У нас так же: kid_cur_dx/kid_cur_flags (их дёргают несколько раз
за кадр из pop_map) и kid_draw читают cur_frame — 5 байт в _DATA.
kid_seq_off (230 Б) оставлен резидентным: его читает kid_set_seq из
pop_ctrl/pop_map, вне страницы.

pop_extract_kid_data.py теперь пишет и kid_data.bin, и урезанный
kid_data.h (тип kframe, размеры, смещения в бинаре, kid_seq_off).

Замер: _CODE 24881 -> 21634 (-3247 Б), куча W2 2076 -> 5315 Б, W3 без
изменений.  Проверено в MAME скриптом: стойка -> бег (кадр 8) -> стоп,
перемещение и коллизия у кромки работают, спрайт рисуется.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:15:16 +03:00
snark13 a5773ab654 docs(PoP): план v2 — размер кода и раскладка по окнам/банкам/страницам
Новый документ applications/PoP/docs/layout_plan_v2.md по свежему замеру
(коммит 1214785): точные размеры окон/модулей/функций/данных, уточнённая
модель банкинга и пошаговый план.

Главное уточнение против v1: из __banked-кода резидент W3 недостижим — и
транзитивно тоже (bank -> pop_map -> pop_bg сломается).  Отсюда целевая
раскладка: W3-резидент = графика, которую зовёт только главный цикл;
W1/W2 = ядро, достижимое отовсюду (включая банки); банки = новая холодная
логика (стражи/боёвка).  Проверено по libbgi: скобка _bgi_begin/_bgi_end
сохраняет и возвращает ТЕКУЩУЮ страницу W3, поэтому примитивы libbgi
можно звать и из банка; нельзя лишь открывать скобку из кода, лежащего
в W3.

Крупнейшие цели: kid_data.h (3745 Б таблиц в _CODE) -> EMM-страница с
портом load_frame/cur_frame; вынос loose/потолка из pop_map в W3 (делает
pop_map bank-safe); дедуп геометрии в pop_geom.c; разгрузка _DATA.

Попутная находка: --w3 принимает ОДИН файл на флаг, поэтому в Makefile
"--w3 pop_trob.c pop_map.c" кладёт в W3 только pop_trob, а pop_map едет
в W1/W2 (в build-каталоге остался устаревший w3_pop_map.rel).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:55:36 +03:00
snark13 68b8fe5851 .gitignore: не версионировать docs/extra и docs/sources
docs/extra — ~570 МБ архивов чужих исходников (525 МБ из них — четыре
почти одинаковых zip'а bad_apple); docs/sources — клоны чужих
репозиториев со своими .git внутри, которые при обычном add стали бы
битыми gitlink-ссылками (без .gitmodules клон их не подтянет).

Материалы остаются на диске, но в историю не попадают: раздувание репо
необратимо без перезаписи истории.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:28:19 +03:00
snark13 64ce6339eb docs: справочники по железу Sprinter + правка gfx_scroll_h
- docs/new/ — сводные справочники (архитектура, BIOS, DSS, память,
  графика, акселератор, IRQ, порты, ввод, звук, известные баги);
- docs/Original/ — первоисточники, из которых они собраны (BIOS, Estex
  DSS, мануалы, описание акселератора), + Форум.doc/.docx в reference;
- libbgi/common/gfx_scroll_h.c — обход бага скролла при ширине >256
  (правка автора: шаг банды 255 и продвижение указателей на cw; старый
  вариант с 256 оставлен закомментированным с TODO);
- удалён applications/PoP/roomtest/hang_variants.png — рабочая раскладка
  из разбора позы виса, в репозитории ей не место.

Большие архивы (docs/extra ~568 МБ, docs/sources с вложенными git-репо
~68 МБ) в коммит НЕ включены — см. обсуждение.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 18:26:31 +03:00
87 changed files with 26202 additions and 918 deletions
+7
View File
@@ -113,3 +113,10 @@ mame/
# Claude Code local settings (per-machine, not for the repo) # Claude Code local settings (per-machine, not for the repo)
.claude/ .claude/
# Тяжёлые справочные материалы, НЕ версионируются: docs/extra — архивы
# исходников (~570 МБ, четыре почти одинаковых bad_apple), docs/sources —
# клоны чужих репозиториев (Sprinter-BIOS, Estex-DSS, SaymanNsk) со своими
# .git внутри (в коммите стали бы битыми gitlink-ссылками).
docs/extra/
docs/sources/
+34
View File
@@ -0,0 +1,34 @@
# Идеи и вопросы «на подумать» (PoP)
Не план работ, а список того, что осознанно отложено: каждая запись —
гипотеза с причиной, по которой её стоит проверить, и с тем, что мешает
сделать это прямо сейчас.
## Отключать мышь на время игры
**Гипотеза.** Мышь на Sprinter — источник прерываний (обёртки RST 30h,
см. memory `mouse_api`). Игре она не нужна вообще: управление —
raw-клавиатура (`<kbd_raw.h>`), которую мы и так забираем у DSS целиком.
Значит каждое мышиное прерывание за кадр — украденные такты в бюджете,
который у нас и без того занят на 86 %.
**Откуда взялось (2026-07-30).** При замере бюджета по 100 кадрам три
кадра выбились до 552–647 К тактов (1.28–1.51 кадра) при типичных 371 К.
Причиной оказалось движение мыши на ХОСТЕ: при неподвижной мыши 225
кадров подряд прошли без единого превышения. То есть эффект реальный и
измеримый, просто в тесте он был наведён извне.
**Что проверить.**
1. Есть ли у драйвера мыши (RST 30h) функция «выключить/включить» —
разобрать список из 14 обёрток; если нет явной, посмотреть, что делает
«hide cursor» и снимает ли она обработчик.
2. Сколько тактов реально стоит одно мышиное прерывание на нашем железе
(замер: breakpoint на входе ISR + totalcycles, при движении мыши).
3. Не ломает ли отключение выход в DSS: состояние обязано
восстанавливаться при `exit`, включая аварийный (atexit).
**Почему не сейчас.** Выигрыш проявляется только когда игрок реально
двигает мышью, то есть в норме его нет; а риск оставить систему без мыши
после выхода — заметный. Делать после того, как закроем стражей и
займёмся бюджетом всерьёз (там же, где батчинг кроссбанковых вызовов и
возможный возврат `pop_bg` в резидент `--w3`).
+446
View File
@@ -0,0 +1,446 @@
# roomtest — план v2: размер кода и раскладка по окнам/банкам/страницам
Статус: **план для отдельной сессии**, составлен 2026-07-29 по свежему замеру.
Заменяет `size_optimization_plan.md` (v1, 2026-07-21): часть его пунктов уже
сделана, часть опиралась на неверную модель банкинга. Документ самодостаточный
— рассчитан на старт с пустого контекста.
Повод: перед стражами и боёвкой (новый код ~5–8 КБ) надо понять, куда он
поместится, и заранее развести код так, чтобы банкованные модули не упёрлись в
ограничения окна W3.
---
## СТАТУС ВЫПОЛНЕНИЯ (обновлено 2026-07-29, коммит ecf5ecf)
| Шаг | Статус | Факт |
|-----|--------|------|
| 1. `kid_data.h` → EMM-страница + `load_frame`/`cur_frame` | **сделан** | `_CODE` −3 247 Б; попутно найден и обойдён баг кодогенерации SDCC (см. ниже) |
| 2. `pop_geom.c` (дедуп геометрии + PRNG) | **сделан** | 41 Б `_CODE`, −11 Б W3; ценность — не байты, а bank-safe слой |
| 3. `pop_map` без вызовов графики | **сделан** (фазы 1a/1b) | через пометки перерисовки, см. ниже |
| 4. Разгрузка/перебалансировка W3 | **сделан** | резидент = `pop_bg` + `pop_gdraw` (отрисовка стража, 2026-07-29); W3 14 376 → 12 819 (свободно 3 565 Б) |
| 5. Данные (`room_modif`, `dl1/dl2`, `_kbdraw_down`) | не начат | потенциал ~1.5 КБ |
| 6. Контракт банка стражей + пробник | **пробник сделан** | `tests/w3bankgfx` — модель подтверждена в MAME, см. ниже |
| 7. Дедуп семейства `draw_tile` | отложен по решению пользователя | «мороки много, выгода не так велика» |
**Замер сейчас против замера §1:** `_CODE` 24 881 → 24 421, куча W2 2 076 → 2 592 Б,
W3-резидент 14 376 → 11 632 (свободно 2 008 → 4 752 Б). Сумма кода упала
на ~3.2 КБ (данные Kid уехали в EMM), остальное — перераспределение.
**Замер 2026-07-29 (после стража).** Появление стража съело кучу до 805 Б;
разгрузка — вынос ОТРИСОВКИ стража в резидент (`pop_gdraw.c`, `--w3`), логика
и состояние остались в W1/W2, чтобы банк `guards.c` их видел (R2). Итог:
`_CODE` 26 149 → 25 703, куча **805 → 1 245 Б**, W3-резидент 11 656 → 12 819
(свободно 4 728 → 3 565 Б), банк 1 — 236 / 16 384 Б. Граница «что резидент»
теперь формулируется одним правилом: **резидент = только то, что рисует и
зовётся исключительно из главного цикла**; всё, что может понадобиться банку,
остаётся в W1/W2.
### Что сделано вместо §5.3 (вынос loose в W3)
Вместо переноса кода между окнами выбран (по обсуждению с пользователем)
**порт архитектуры оригинала**: логика ставит пометку, отрисовка идёт
отдельным проходом — `set_redraw_*` (seg007) + `redraw_needed` (seg008:0178).
Появился `pop_redraw.c/.h`; `pop_trob` и `pop_map` больше не рисуют тайлы.
Наши самодельные счётчики (`spike_rest`, `button_rest`, `ldoor_rest`,
`loose_bake`, `loose_rest`, `ceil_rest`, `ceil_bake`, `land_bake`) удалены —
их роль (вторая страница дабл-буфера) взял счётчик страниц в пометке.
**Два исключения остались** (обе — функции ТОЛЬКО главного цикла, звать из
банка нельзя):
- `pop_process_trobs` — пламя факела и пузырёк зелья (покадровый оверлей);
- `pop_loose_tick` — падающий кусок (mob): spawn/tick/pos. В оригинале это
отдельная подсистема (`mobs` + `draw_moving`), разделение на логику и
отрисовку — задел следующей фазы.
### Пробник банка (2026-07-29): модель ПОДТВЕРЖДЕНА
`tests/w3bankgfx` (huge + `--w3 res.c` + `--bank 1=bank1.c`, графика 256):
- банк рисует примитивом libbgi НАПРЯМУЮ — работает; страница W3 внутри
банка до блита, после блита и после возврата из вызванной им W1/W2-функции
одна и та же (0xF0), резидент — 0xF3. То есть `_bgi_begin`/`_bgi_end`
корректно возвращают ИМЕННО банковую страницу (правило R4);
- вызов W1/W2-функции из банка работает, и она тоже может рисовать;
- резидент W3 жив и вызывается после возврата из банка (R3).
**Дополнительно выяснено (важно для стражей):** писучие статики
`__banked`-модуля линкуются В СТРАНИЦУ БАНКА (0x1C000+) — снаружи их не
прочитать, из W1/W2 по 0xC000 видна резидентная страница. Значит всё
состояние банкованного кода (позиции стражей, таймеры боя) обязано жить в
W1/W2 как обычные глобалы, а банк — только код.
Ещё одна мина, найденная там же: инлайновый `in a,(#0xE2)` посреди тела
функции затирает A, куда SDCC уже положил параметр (у нас из-за этого цвет
заливки стал номером страницы, и «резидент не рисовал»). Читать порт
отдельной `__naked`-функцией.
### Найденная по дороге ловушка компилятора
`(const T *)КОНСТАНТА + var*K` SDCC 4.5 может собрать неверно: умножение
делает в 16 битах, а потом берёт только младший байт (`ld c,l` / `inc b`).
Кадры Kid с индексом ≥ 52 читались из чужой строки таблицы, у бега/шага
пропадал `FRAME_NEEDS_FLOOR` и персонаж проваливался сквозь пол. Лечение —
считать адрес в `uint16_t` и кастовать один раз. Тот же паттерн в
`pop_level.c` компилируется ПРАВИЛЬНО, т.е. полагаться на «у соседа
работает» нельзя. Подробности: memory `sdcc_z80_const_ptr_index_bug`.
---
## 0. Что уже сделано из v1 (не повторять)
- `--opt-code-size` и `--max-allocs 100000` **уже включены по умолчанию** в
`bin/sprinter-cc` (v1 §2.1 закрыт, выигрыш получен).
- Лишние блиты переднего слоя убраны (v1 §7 п.0): `fore_tile` больше не рисует
`bottom_id`, `_CODE` 388 Б.
- `gfx_blit_noclip` в libbgi (v1 §8 шаг 1): фоновые блиты в 2.9× дешевле.
- `--w3` как резидент окна 3 реализован и обкатан (memory `w3_resident_code`).
---
## 1. ЗАМЕР (сборка 2026-07-29, коммит 1214785)
Команда: `--memory small --gfx 256 --w3 pop_trob.c pop_map.c --w3 pop_bg.c`.
### 1.1 Окна
| Область | Занято | Свободно | Примечание |
|---|---|---|---|
| W1+W2 `_CODE` | 24 881 Б | — | 0x4100…0xA231 |
| W1+W2 `_HOME`+`_GSINIT`+`_DATA`+`_BSS` | ~4 240 Б | — | до 0xB2E4 |
| **W1+W2 куча** | 0 (никто не malloc'ит) | **2 076 Б** | 0xB2E4…0xBB00 |
| W1+W2 стек | — | 1 279 Б | 0xBB00…0xBFFE |
| **W3 резидент** | 14 376 Б | **2 008 Б** | 0xC000…0xF828 |
| EMM-страницы | 37 атласов + 1 уровень | ~215 страниц свободно | `sprinter_emm_budget` |
**Итого запаса до стены: ≈ 4 КБ** (2 КБ в W1/W2 + 2 КБ в W3). Стражи туда
не влезут.
### 1.2 Код по модулям (точно, из `.rel`)
```
W1/W2 (_CODE 24 881): W3 резидент (_W3CODE 14 376):
pop_kid 6 963 pop_bg 11 643
pop_map 6 157 pop_trob 2 733
roomtest 2 583
pop_level 1 425
pop_ctrl 1 145
crt0 333
libc+libbgi ~6 275
```
### 1.3 Крупнейшие функции/данные (из `.lst`)
```
pop_bg : draw_tile 3080, other_overlay_tile 1146, wall_pattern 944,
mob_render 720, mob_tick_one 661, overlay_mid_tile 498,
fore_only_tile 409, climb_overlay_tile 391, tile_table 371
pop_map : check_loose_fall_on_kid 674, check_bumped 567, jump_up_or_grab 413,
get_tile 266, do_knock 243, check_press 238, check_leave 212
pop_kid : kid_seqtbl 2310 + kid_frames 1205 + kid_seq_off 230 = 3745 Б ДАННЫХ
(в _CODE!), собственно кода ~3.2 КБ
roomtest : enter_room+main ~2.1 КБ
pop_level: room_bg_ptr 1084 (+ 515 Б таблиц LINKLOC/LINKMAP в _DATA)
```
### 1.4 `_DATA` (3 710 Б)
```
pop_trob 963 (room_modif[24][30] = 720 + trobs + rest-массивы)
pop_level 515 (копии LINKLOC/LINKMAP уровня)
pop_map 163, roomtest 141, pop_kid 130, pop_bg 115, pop_ctrl 13
libc: _irq_state 818, _kbdraw_state 515, _gfx_pal_buf 256, прочее ~200
```
### 1.5 Находки замера (мелкие, но чинить)
1. **`--w3` берёт ОДИН файл на флаг.** В `Makefile` написано
`--w3 pop_trob.c pop_map.c --w3 pop_bg.c`, и это значит «W3 = pop_trob и
pop_bg», а `pop_map.c` компилируется как обычный исходник в W1/W2. Судя по
`.sprinter-cc-roomtest/w3_pop_map.rel` (устаревший артефакт), когда-то
pop_map был в W3. **Решить осознанно** (см. §4) и записать явно:
`--w3 pop_trob.c --w3 pop_bg.c`.
2. `libc` тянет `_irq_state` 818 Б + `_kbdraw_state` 515 Б в `_DATA`.
`__irq_vec_buf` (513 Б) — таблица векторов IM2; `__kbdraw_down` (512 Б) —
битмап клавиш на 512 скан-кодов. Оба можно ужать (см. §5.4), это ~0.7 КБ
в самом дефицитном окне.
---
## 2. ПРАВИЛА ПЛАТФОРМЫ, ОТ КОТОРЫХ ПЛЯШЕТ РАСКЛАДКА
Это главное, что изменилось по сравнению с v1: модель банкинга уточнена по
`bin/sprinter-cc` (справка `--w3`/`--bank`) и по коду libbgi.
**(R1) Резидент W3 (`--w3`) и банки W3 (`--bank`) делят одно окно.**
Резидент лежит на своей странице 0xC000…0xFFFF; трамплин на время вызова
`__banked` подменяет страницу W3 на банковую и возвращает резидентную назад.
**(R2) Из банка резидент W3 НЕДОСТИЖИМ — и транзитивно тоже.**
Пока исполняется банк, резидентной страницы в адресном пространстве нет.
Значит нельзя не только `bank → pop_bg()`, но и `bank → pop_map() → pop_bg()`.
**Это ключевое ограничение при выборе, что делать банком.**
**(R3) Резидент → банк работает** (через трамплин в W1), резидент → W1/W2 —
тоже.
**(R4) Графические примитивы libbgi звать можно откуда угодно.**
`_bgi_begin` читает текущую страницу W3 из порта 0xE2, а `_bgi_end` её
возвращает — то есть скобка корректна и из банка, и из резидента. Нельзя
только одно: **звать `_bgi_begin`/`_bgi_end` ИЗ кода, который сам лежит в W3**
(после подмены страницы исчезнет исполняемый код — проверено, белый экран).
Поэтому `pop_bg` (резидент W3) обязан пользоваться готовыми примитивами
(`gfx_blit*`, `bar`, …), а батчинг скобки на весь `draw_tile` (v1 §8 шаг 1)
для него **невозможен** без переноса самого `draw_tile` в W1/W2.
**(R5) `--w3` кладёт в W3 код И rodata модуля** (`--codeseg/--constseg
W3CODE`), а писучие статики оставляет в `_DATA` (W2). То есть `const`-таблицы
переносятся в W3 бесплатно вместе с модулем (так уже лежит `tile_table` 371 Б).
**(R6) Данные в EMM-странице читаются, только пока страница в окне.**
`gfx_w0_map(page)` / `gfx_w0_unmap()` — окно W0 (0x0000…0x3FFF), первые 0x100
занимает ISR-стаб. Так уже работает `pop_level`. Цена — пара `OUT` на
маппинг, поэтому годится для «пачками», а не для чтения по байту в горячем
цикле.
---
## 3. ЧТО ДЕЛАТЬ НЕЛЬЗЯ (анти-паттерны, чтобы не потерять время)
- **Нельзя банковать `pop_bg`.** Он вызывается из pop_map, pop_trob, roomtest,
pop_kid — то есть из главного цикла на каждом кадре; плюс он сам держит
`tile_table` и всю отрисовку. Банк дал бы трамплин на каждый блит.
- **Нельзя банковать `pop_map`, пока `pop_map` зовёт `pop_bg`** (R2). Сейчас
зовёт: `pop_loose_tick` и компания (~30 вызовов графики).
- **Нельзя тащить `kid_frames` в EMM «в лоб»**: он читается несколько раз за
кадр из коллизии (`kid_cur_dx`/`kid_cur_flags``dx_weight`,
`char_x_forward_edge`, …). Нужен кэш кадра (см. §5.1) — иначе маппинг
страницы окажется в горячем пути.
- **Нельзя «причёсывать» семейство `draw_tile` ради экономии, не имея
пиксельного теста.** Мы неделю выравнивали слои по SDLPoP; любой рефактор
этой зоны проверять диффом страниц (заморозка кадра клавишей `1` + сравнение
VRAM обеих страниц, приём из memory `mame_mcp_bridge`).
---
## 4. ЦЕЛЕВАЯ РАСКЛАДКА
Принцип: **W3-резидент = «толстая графика, которую зовёт только главный цикл»;
W1/W2 = ядро, которое должно быть достижимо ОТОВСЮДУ (включая банки); банки =
новая холодная логика (стражи, боёвка, будущие уровни)**.
```
W1/W2 (всегда отображено) W3 резидент (стр. 0xC000) Банки W3
────────────────────────── ───────────────────────── ─────────
libc + libbgi pop_bg (отрисовка тайлов) guards.c
pop_kid (интерпретатор+рисование) pop_trob (анимации тайлов) fight.c
pop_map (коллизия/физика/предметы) pop_loose.c (loose+потолок) debug/roomnav
pop_geom (общая геометрия/тайлы) enter_room-часть roomtest?
pop_ctrl (ввод/диспетчер)
roomtest (главный цикл)
```
Почему так:
- **`pop_map` остаётся в W1/W2** — его зовут и главный цикл, и (в будущем)
банк стражей; в W3 его класть нельзя именно из-за R2. Для этого из него надо
вынести графическую часть (loose/потолок) — она уезжает в W3 к `pop_bg`
(§5.3). После выноса `pop_map` становится **чистой логикой без единого
вызова графики** — тот самый bank-safe API.
- **`pop_kid` остаётся в W1/W2**: `play_seq`/`kid_set_seq`/`Kid` нужны и
стражам (у стражей ТА ЖЕ seqtbl), а `kid_draw` зовёт только libbgi (R4).
- **`pop_trob` остаётся резидентом**: его зовёт только главный цикл, и он сам
зовёт `pop_bg` — идеальный житель W3.
- **Банк стражей не зовёт ничего из W3.** Рисование стражей — либо через
libbgi напрямую (R4), либо (лучше) резидентный `guard_draw()` в W1/W2 рядом
с `kid_draw`, а банк только считает состояние. Тот же приём мы уже
используем для `pop_item_taken`/`pop_loose_fell`/`pop_ceil_fell`: банк
выставляет флаг — резидент рисует.
---
## 5. ПЛАН РАБОТ
Порядок выбран так, чтобы каждый шаг был проверяем отдельно и давал место
следующему.
### Шаг 1. `kid_data.h` (3 745 Б) → EMM-страница + порт `load_frame` — **самый большой выигрыш**
Сейчас `kid_seqtbl` (2310) + `kid_frames` (1205) + `kid_seq_off` (230) лежат в
`_CODE` окна W1/W2 — это 15 % всего дефицитного пространства.
Как переносить:
1. `pop_extract_kid_data.py` дополнительно пишет `kid_data.bin` (те же три
таблицы подряд, фиксированные смещения).
2. Грузим её в отдельную EMM-страницу тем же способом, что уровень
(`pop_level_load` — готовый образец), хэндл держим в `pop_kid`.
3. **Порт `load_frame` (seg006) и глобала `cur_frame`** — в оригинале ровно
так и сделано: раз за тик кадр копируется в структуру, а весь остальной код
читает `cur_frame`, а не таблицу. У нас `kid_cur_dx()/kid_cur_flags()`
станут чтением из `cur_frame` (5 байт в `_DATA`).
4. `play_seq` оборачивается в один `gfx_w0_map(kid_data_page)``unmap` на
вызов (в тике, не в отрисовке — конфликта с атласом в W0 нет).
Выигрыш: **3 745 Б из W1/W2**, цена — один маппинг страницы за тик и 5 байт
`_DATA`. Дополнительный бонус: `load_frame`/`cur_frame` — шаг К СХОДСТВУ с
оригиналом, а не отход от него.
Риск: сломать `play_seq` (сердце анимации). Проверка: прогон по комнатам с
эталонными позами (вис, подтягивание, прыжки, подъём меча).
### Шаг 2. Модуль `pop_geom.c` — дедуп + bank-safe фундамент
Сейчас продублировано между модулями:
| что | где | сколько |
|---|---|---|
| `y_to_row`/`y_to_row_mod4` | pop_bg + pop_map | 2 копии |
| `char_dx_forward` | pop_kid + pop_map | 2 копии |
| `x_bump[20]` | pop_kid (uint8) + pop_map (int16) | 20 + 40 Б, РАЗНЫЕ типы |
| `y_land[5]` | pop_kid + pop_map | 10 + 10 Б |
| `tile_is_floor` | pop_map (+ проверка кодов в roomtest) | 2 места |
| 32-битный LCG `prandom` | pop_bg (`prandom`) + pop_trob (`trob_prandom`) | 2 копии по ~60 Б + 2 сида |
Собрать в один W1/W2-модуль `pop_geom.c`: таблицы `x_bump/y_land/dir_front/
dir_behind`, `y_to_row`, `char_dx_forward`, `get_tile_div_mod(_m7)`,
`tile_is_floor`, `prandom`. Выигрыш прямой — сотни байт (оценка 250–400 Б),
но главное — **это и есть тот «чистый» API, который потом сможет звать банк**
(R2): вся геометрия оказывается в W1/W2 по определению.
Осторожно: `prandom` у pop_bg и pop_trob — РАЗНЫЕ последовательности с разными
сидами (стены vs фазы факелов). Объединять функцию можно, **сиды — нет**:
передавать сид указателем/по индексу, иначе поедет раскладка кладки.
### Шаг 3. Вынести loose/потолок из `pop_map` в W3
`pop_map` — единственный модуль W1/W2, который зовёт графику, и делает это
ровно в одном логическом блоке: `pop_loose_tick` + `check_press` + `do_knock` +
`fell_on_your_head` + `check_loose_fall_on_kid` + плита-потолок (~1.4–2 КБ).
Вынести их в `pop_loose.c`, собираемый `--w3` рядом с `pop_bg`/`pop_trob`.
Тогда:
- `pop_map` = чистая логика (bank-safe, R2 соблюдён);
- W1/W2 худеет ещё на ~1.5–2 КБ;
- W3 растёт на столько же — а место там появится после шага 4.
### Шаг 4. Перебалансировка резидента W3
После шага 3 в W3 будет тесно (14.4 + 2 ≈ 16.4 КБ > 16 КБ). Разгружаем:
1. **`wall_pattern` (944 Б) + `mob_render`/`mob_tick_one` (1381 Б)** — кандидаты
на переезд в W1/W2: их зовёт только `pop_bg`/`pop_loose`, но сами они уже
пользуются только libbgi (R4), значит из W1/W2 работают и остаются
достижимыми из банка.
2. `tile_table` и мелкие const-таблицы pop_bg (371 + ~300 Б) можно унести в
EMM-страницу **уровня** (там ~13.8 КБ свободно) — но только если чтение
происходит под уже замапленной страницей. Сейчас `draw_tile` читает
`tile_table` ВНЕ W0-контекста → потребуется явный маппинг на тайл. **Не
делать раньше замера**: 30 тайлов на входе в комнату × map/unmap — терпимо,
а вот в покадровых редроях (пики/loose/кнопка) — уже горячий путь.
3. Если и этого мало — `enter_room` (~2.1 КБ, зовётся только при смене комнаты)
переносится в резидент W3 или в БАНК (он вызывается из главного цикла =
резидента, значит банк допустим по R3).
### Шаг 5. Данные
1. **`room_modif[24][30]` = 720 Б** (pop_trob, `_DATA`). Нужен произвольный
доступ каждый кадр (анимации, ворота) — в EMM не годится. Но 24 комнаты ×
30 байт хранятся ЦЕЛИКОМ, хотя одновременно живут modif'ы только текущей
комнаты и соседей по швам. Вариант: хранить полный массив в EMM-странице
уровня, а в `_DATA` держать кэш на 2–3 комнаты (свою + левого/правого
соседа) с записью обратно при смене комнаты. Выигрыш ~600 Б, цена —
аккуратность на швах (кнопка в одной комнате открывает ворота в другой).
**Делать последним** — это самая «тонкая» правка по семантике.
2. **`dl1[256]`+`dl2[256]` = 512 Б** (pop_level, `_DATA` — копии LINKLOC/
LINKMAP уровня) — читаются при нажатии кнопки
и при отрисовке нажатой кнопки. Кандидат на чтение прямо из страницы
уровня (она и так маппится) — но проверить, что `pop_doorlink2` не зовётся
из отрисовки в тот момент, когда в W0 атлас. Выигрыш ~500 Б.
3. **`_kbdraw_down[512]` 512 Б** (libc): проверено — это **байт на скан-код**
(`libc/kbd/_kbdraw_state.c`), хотя комментарий называет его битовой картой.
Упаковка в биты даёт −448 Б, но добавляет сдвиг/маску в ISR-трамплин и в
`kbd_raw_down`. Трогать осторожно: raw-клавиатура уже дважды была
источником залипаний (memory `kbd_raw_fifo_drain`,
`kbd_overrun_wipe_modifiers`) — правку сопровождать прогоном docs/kbd-games.
4. **`__irq_vec_buf` 513 Б** (libc IM2): таблица векторов обязана быть
выровнена и полна — не трогать.
### Шаг 6. Контракт банка стражей (проектируется ДО написания кода)
Когда дойдём до стражей:
- `guards.c` собирается `--bank 1=guards.c`, режим `huge` (или `big` с
`BANKED=W1`, если W3 окажется тесен для трамплинов).
- **Банк зовёт только:** `pop_map` (чистая логика после шага 3), `pop_kid`
(`play_seq`, `kid_set_seq`, `cur_frame`), `pop_geom`, libc/libbgi.
- **Банк НЕ зовёт:** `pop_bg`, `pop_trob`, `pop_loose` (резидент W3) — ни
прямо, ни через промежуточные функции. Нужна отрисовка — выставляет флаг,
рисует резидент (идиома `pop_item_taken`).
- Первым делом — **пробник** (`tests/` или `--bank` на пустышке): банк зовёт
`pop_map`-функцию, та зовёт libbgi-примитив; убедиться в MAME, что скобка
W3 корректно возвращает банковую страницу (R4) — это проверка модели, а не
веры в неё.
### Шаг 7. Мелкий дедуп в `pop_bg` (после того, как появится тест страниц)
- Пять функций-редроев (`pop_spike_redraw`, `pop_loose_shake_draw`,
`pop_floor_bake`, `pop_button_redraw`, `pop_leveldoor_redraw`) отличаются
только прямоугольником heal, банком и набором тайлов — свести к одному
параметризованному хелперу (оценка −150…250 Б).
- `env_b/wall_b/fore_b/pot_b` — четыре одинаковых обёртки над `blit_b`
(оставить: экономия единицы байт, читаемость дороже).
- `overlay_mid_tile` / `fore_only_tile` / `climb_overlay_tile` / `draw_tile`
— общая структура «взять code/lcode, посчитать x/dmy/dby, разобрать слои».
Тут экономия потенциально сотни байт, но это **та самая зона риска из §3**
только с пиксельным диффом до/после и по одному слою за раз.
---
## 6. Ожидаемый итог
| Шаг | W1/W2 | W3 | Риск |
|---|---|---|---|
| 1. kid_data → EMM + load_frame | **3 745** | — | средний (сердце анимации) |
| 2. pop_geom (дедуп) | 250…400 | — | низкий |
| 3. loose → W3 | 1 500…2 000 | +1 500…2 000 | низкий (перенос как есть) |
| 4. разгрузка W3 (wall_pattern, mob) | +2 300 | 2 300 | низкий |
| 5. данные (room_modif, LINKLOC, kbd) | 1 000…1 600 | — | средний/высокий |
| 7. дедуп редроев pop_bg | — | −150…250 | средний |
Суммарно: **W1/W2 освобождается ~4.5–6 КБ**, W3 остаётся примерно в нынешнем
объёме, но становится «правильно заполненным» — в нём только то, что банк
никогда не позовёт. Плюс открывается путь к банкам: стражи и боёвка получают
до 16 КБ на банк, не трогая резидент.
---
## 7. Как мерить и проверять (обязательно к каждому шагу)
1. **До/после по `.rel`** — точные размеры на модуль:
`for f in .sprinter-cc-roomtest/*.rel; do grep '^A ' $f; done`
(области `_CODE`/`_W3CODE`/`_DATA`). Итоги окон печатает сам `sprinter-cc`.
2. **Функции** — из `.lst` (метки `_name:` и адреса), скрипт в истории этой
сессии; полезно ловить «функция распухла после рефактора».
3. **MAME**: любой перенос кода между окнами/страницами — это класс «молча
ломается» (`sprinter_memory_modes`). Минимум: комната 1 (loose), 12
(вис/подтягивание), 15 (меч), 9 (дверь уровня), 6 (кнопка/ворота).
4. **Пиксельный дифф** для правок отрисовки: заморозить кадр (`1`), сравнить
обе страницы дабл-буфера через `vram` (см. memory `mame_mcp_bridge`) и/или
сверить с эталонным рендером `render_room.py`.
5. **Скорость** — после шагов 1 и 4 замерить кадр маркерами в порт 0xFE
(приём из v1 §8), чтобы маппинг страницы за тик не съел бюджет.
## 8. Ссылки
- `bin/sprinter-cc` — справка по `--w3`, `--bank`, `--memory`, `--memory-manual`.
- `runtime/crt0_banked.s`, `runtime/bank.s` — трамплины и захват резидентной
страницы W3.
- `libbgi/common/_bgi_begin.c` / `_bgi_end.c` — механика скобки W3 (R4).
- memory: `w3_resident_code`, `pop_banking_architecture`, `sdcc_banking`,
`bank_local_data_pattern`, `sprinter_memory_modes`, `memory_modes_implemented`,
`sprinter_emm_budget`, `mame_mcp_bridge`, `avoid_32bit_arith_z80`,
`libc_one_function_per_module`.
- `applications/PoP/docs/size_optimization_plan.md` — v1 (замер 2026-07-21,
раздел §8 про скорость отрисовки актуален и не дублируется здесь).
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+43 -6
View File
@@ -5,9 +5,23 @@ PROJ_ROOT := $(abspath $(CURDIR)/../../..)
EXAMPLE := roomtest EXAMPLE := roomtest
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе # small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge). # CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
MEMORY ?= small MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи)
EXTRA_FLAGS ?= --gfx 256 --w3 pop_trob.c pop_map.c --w3 pop_bg.c # Резидента --w3 БОЛЬШЕ НЕТ: отрисовка уехала в БАНК 2 (pop_bg + pop_gdraw
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c # в одной странице — вызовы между ними прямые, если функция не помечена
# __banked). Так снято главное ограничение резидента: из банка его было
# не достать ни прямо, ни транзитивно, а банк→банк работает (трамплин
# сохраняет страницу окна на СТЕКЕ, проверено вложенной цепочкой
# банк1→банк2→банк1 в MAME 2026-07-30). Теперь в банки можно уводить и
# логику, которая рисует. См. docs/layout_plan_v2.md §2, memory
# sdcc_banked_call_rules.
EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 2=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c $(PROF_FLAGS)
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
# MAME = время фазы в растровых строках.
PROF ?= 1
PROF_FLAGS := -DPROF_BORDER=$(PROF)
EXTRA_SRCS := pop_state.c pop_kid.c pop_ctrl.c pop_level.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c
BG_DIR := $(CURDIR)/../poc/res/bg BG_DIR := $(CURDIR)/../poc/res/bg
KID_DIR := $(CURDIR)/../poc/res/kid KID_DIR := $(CURDIR)/../poc/res/kid
@@ -22,16 +36,39 @@ BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2.
# ЕДИНАЯ игровая (env+wall+kid). # ЕДИНАЯ игровая (env+wall+kid).
KID_PAGES := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl) KID_PAGES := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl)
KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
KID_BIN := $(KID_DIR)/kid_data.bin
GUARD_DIR := $(CURDIR)/../poc/res/guard
GUARD_DATA := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
$(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl
EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(LVL_DATA) EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(LVL_DATA)
include $(PROJ_ROOT)/app.mk include $(PROJ_ROOT)/app.mk
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
# чтобы корень диска не зарастал десятками .atl.
# BG\ — фон (env/wall/fore/pot)
# KID\ — персонаж (kid0..27, палитра, меч, таблицы анимации)
# GUARD\ — стражи (появятся здесь же)
# LEVELS\ — уровни
hdd: $(EXAMPLE).exe
$(PROJ_ROOT)/toolchain/make_hdd.sh $(PROJ_ROOT)/mame/v306/IMG/test_hdd.chd \
$(EXAMPLE).exe \
$(foreach f,$(BG_DATA),BG:$(f)) \
$(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \
$(foreach f,$(GUARD_DATA),GUARD:$(f)) \
LEVELS:$(LVL_DATA)
TC := $(PROJ_ROOT)/applications/PoP/toolchain TC := $(PROJ_ROOT)/applications/PoP/toolchain
$(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py $(BG_DATA): $(TC)/pop_pack_bg.py $(TC)/render_room.py
cd $(TC) && python3 pop_pack_bg.py cd $(TC) && python3 pop_pack_bg.py
$(GUARD_DATA): $(TC)/pop_pack_guard.py
cd $(TC) && python3 pop_pack_guard.py
$(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
cd $(TC) && python3 pop_pack_kid.py cd $(TC) && python3 pop_pack_kid.py
kid_data.h: $(TC)/pop_extract_kid_data.py kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
cd $(TC) && python3 pop_extract_kid_data.py cd $(TC) && python3 pop_extract_kid_data.py
$(EXAMPLE).exe: pop_bg.c pop_bg.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(LVL_DATA) $(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_gdraw.c pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
+26
View File
@@ -0,0 +1,26 @@
/*
* guards.c — ЛОГИКА стража, собирается в БАНК (--bank 1=guards.c, окно W3).
*
* Правила банка (проверено пробником tests/w3bankgfx):
* - состояние держим в W1/W2 (pop_guard.c): статики банка линкуются в
* страницу банка и снаружи не читаются;
* - звать можно W1/W2-код (pop_map, pop_kid, pop_geom, libc, примитивы
* libbgi) — скобка _bgi_begin/_bgi_end возвращает страницу БАНКА;
* - звать НЕЛЬЗЯ резидент W3 (pop_bg) — ни напрямую, ни транзитивно:
* пока исполняется банк, резидентной страницы в адресном пространстве
* нет. Поэтому фон/окклюзию за стража рисует главный цикл.
*
* Сейчас — каркас: тик есть, ИИ и боёвка (порт seg005 control_guard*,
* seg004 fight/collision) добавляются сюда.
*/
#include "pop_guard.h"
#include "pop_kid.h"
void pop_guard_tick(void) __banked
{
if (Guard.charid == 0) return; /* стража в комнате нет */
/* Анимация: та же seqtbl, что у Кида, но своя таблица кадров —
* выбирается по Char.charid внутри load_frame (seg006:0293). */
pop_guard_play();
/* TODO: порт control_guard/fight (seg005/seg004) — состояние в Guard. */
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

+11 -440
View File
@@ -8,449 +8,20 @@
#define SEQTBL_BASE 0x196Eu #define SEQTBL_BASE 0x196Eu
/* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid). */ /* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid).
* САМА таблица кадров и seqtbl лежат в kid_data.bin (EMM-
* страница); здесь только тип и размеры. */
typedef struct { uint8_t image; int8_t dx, dy; uint8_t flags, sword; } kframe; typedef struct { uint8_t image; int8_t dx, dy; uint8_t flags, sword; } kframe;
#define KID_NFRAMES 241 #define KID_NFRAMES 241
static const kframe kid_frames[KID_NFRAMES] = {
{255, 0, 0,0x00,0x00},
{ 0, 1, 0,0xC4,0x00},
{ 1, 1, 0,0x44,0x00},
{ 2, 3, 0,0x47,0x00},
{ 3, 4, 0,0x48,0x00},
{ 4, 0, 0,0xE6,0x00},
{ 5, 0, 0,0x49,0x00},
{ 6, 0, 0,0x4A,0x00},
{ 7, 0, 0,0xC5,0x00},
{ 8, 0, 0,0x44,0x00},
{ 9, 0, 0,0x47,0x00},
{ 10, 0, 0,0x4B,0x00},
{ 11, 0, 0,0x43,0x00},
{ 12, 0, 0,0xC3,0x00},
{ 13, 0, 0,0x47,0x00},
{ 14, 0, 0,0x43,0x09},
{ 15, 0, 0,0xC3,0x00},
{ 16, 0, 0,0x44,0x00},
{ 17, 0, 0,0x46,0x00},
{ 18, 0, 0,0x48,0x00},
{ 19, 0, 0,0x89,0x00},
{ 20, 0, 0,0x0B,0x00},
{ 21, 0, 0,0x8B,0x00},
{ 22, 0, 0,0x11,0x00},
{ 23, 0, 0,0x07,0x00},
{ 24, 0, 0,0x05,0x00},
{ 25, 0, 0,0xC1,0x00},
{ 26, 0, 0,0xC6,0x00},
{ 27, 0, 0,0x43,0x00},
{ 28, 0, 0,0x48,0x00},
{ 29, 0, 0,0x42,0x00},
{ 30, 0, 0,0x42,0x00},
{ 31, 0, 0,0xC2,0x00},
{ 32, 0, 0,0xC2,0x00},
{ 33, 0, 0,0x43,0x00},
{ 34, 0, 0,0x48,0x00},
{ 35, 0, 0,0xCE,0x00},
{ 36, 0, 0,0xC1,0x00},
{ 37, 0, 0,0x45,0x00},
{ 38, 0, 0,0x8E,0x00},
{ 39, 0, 0,0x0B,0x00},
{ 40, 0, 0,0x8B,0x00},
{ 41, 0, 0,0x8A,0x00},
{ 42, 0, 0,0x01,0x00},
{ 43, 0, 0,0xC4,0x00},
{ 44, 0, 0,0xC3,0x00},
{ 45, 0, 0,0xC3,0x00},
{ 46, 0, 0,0xA5,0x00},
{ 47, 0, 0,0xA4,0x00},
{ 48, 0, 0,0x66,0x00},
{ 49, 4, 0,0x67,0x00},
{ 50, 3, 0,0x66,0x00},
{ 51, 1, 0,0x44,0x00},
{ 64, 0, 0,0xC2,0x00},
{ 65, 0, 0,0x41,0x00},
{ 66, 0, 0,0x42,0x00},
{ 67, 0, 0,0x00,0x00},
{ 68, 0, 0,0x00,0x00},
{ 69, 0, 0,0x80,0x00},
{ 70, 0, 0,0x00,0x00},
{ 71, 0, 0,0x80,0x00},
{ 72, 0, 0,0x00,0x00},
{ 73, 0, 0,0x80,0x00},
{ 74, 0, 0,0x00,0x00},
{ 75, 0, 0,0x00,0x00},
{ 76, 0, 0,0x80,0x00},
{255, 0, 0,0x00,0x00},
{ 80, -2, 0,0x41,0x00},
{ 81, -2, 0,0x41,0x00},
{ 82, -1, 0,0xC2,0x00},
{ 83, -2, 0,0x42,0x00},
{ 84, -2, 0,0x41,0x00},
{ 85, -2, 0,0x41,0x00},
{ 86, -2, 0,0x41,0x00},
{ 87, -1, 0,0x07,0x00},
{ 88, -1, 0,0x05,0x00},
{ 89, 2, 0,0x07,0x00},
{ 90, 2, 0,0x07,0x00},
{ 91, 2, -3,0x00,0x00},
{ 92, 2, -10,0x00,0x00},
{ 93, 2, -11,0x80,0x00},
{ 94, 3, -2,0x43,0x00},
{ 95, 3, 0,0xC3,0x00},
{ 96, 3, 0,0xC3,0x00},
{ 97, 3, 0,0x63,0x00},
{ 98, 4, 0,0xE3,0x00},
{ 28, 0, 0,0x00,0x00},
{ 99, 7, -14,0x80,0x00},
{100, 7, -12,0x80,0x00},
{101, 4, -12,0x00,0x00},
{102, 3, -10,0x80,0x00},
{103, 2, -10,0x80,0x00},
{104, 1, -10,0x80,0x00},
{105, 0, -11,0x00,0x00},
{106, -1, -12,0x00,0x00},
{107, -1, -14,0x00,0x00},
{108, -1, -14,0x00,0x00},
{109, -1, -15,0x80,0x00},
{110, -1, -15,0x80,0x00},
{111, 0, -15,0x00,0x00},
{255, 0, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{112, 0, 0,0xC6,0x00},
{113, 0, 0,0x46,0x00},
{114, 0, 0,0xC5,0x00},
{115, 0, 0,0x45,0x00},
{116, 0, 0,0xC2,0x00},
{117, 0, 0,0xC4,0x00},
{118, 0, 0,0xC5,0x00},
{119, 0, 0,0x46,0x00},
{120, 0, 0,0x47,0x00},
{121, 0, 0,0x47,0x00},
{122, 0, 0,0x49,0x00},
{123, 0, 0,0xC8,0x00},
{124, 0, 0,0xC9,0x00},
{125, 0, 0,0x49,0x00},
{126, 0, 0,0x45,0x00},
{127, 2, 0,0x45,0x00},
{128, 2, 0,0xC5,0x00},
{129, 0, 0,0xC3,0x00},
{255, 0, 0,0x00,0x00},
{133, 0, 0,0x43,0x00},
{134, 0, 0,0xC4,0x00},
{135, 0, 0,0xC5,0x00},
{136, 0, 0,0x48,0x00},
{137, 0, 0,0x6C,0x00},
{138, 0, 0,0xEF,0x00},
{139, 0, 0,0x63,0x00},
{140, 0, 0,0xC3,0x00},
{141, 0, 0,0x43,0x00},
{142, 0, 0,0x43,0x00},
{143, 0, 0,0x44,0x00},
{144, 0, 0,0x44,0x00},
{172, 0, 1,0xC1,0x00},
{173, 0, 1,0xC7,0x00},
{145, 0, -12,0x01,0x00},
{146, 0, -21,0x00,0x00},
{147, 1, -26,0x80,0x00},
{148, 4, -32,0x80,0x00},
{149, 6, -36,0x81,0x00},
{150, 7, -41,0x82,0x00},
{151, 2, 17,0x42,0x00},
{152, 4, 9,0xC4,0x00},
{153, 4, 5,0xC9,0x00},
{154, 4, 4,0xC8,0x00},
{155, 5, 0,0x69,0x00},
{156, 5, 0,0xE9,0x00},
{157, 5, 0,0xE8,0x00},
{158, 5, 0,0x69,0x00},
{159, 5, 0,0x69,0x00},
{184, 0, 2,0x80,0x10},
{174, 0, 2,0x80,0x1A},
{175, 3, 2,0x00,0x12},
{176, 7, 2,0xC4,0x16},
{177, 10, 2,0x00,0x15},
{178, 7, 2,0x80,0x17},
{179, 4, 2,0x80,0x19},
{180, 0, 2,0xCE,0x18},
{181, 0, 2,0xCD,0x0F},
{182, 3, 2,0x00,0x14},
{183, 3, 2,0x00,0x1F},
{184, 0, 2,0x80,0x10},
{185, 0, 2,0x80,0x11},
{186, 0, 2,0x00,0x20},
{187, 0, 2,0x80,0x21},
{188, 2, 2,0xC3,0x22},
{ 14, 0, 0,0x43,0x00},
{189, 7, 2,0x80,0x13},
{190, 1, 2,0x80,0x0E},
{191, 0, 2,0x80,0x1B},
{181, 0, 2,0xCD,0x0F},
{181, 0, 2,0xCD,0x0F},
{112, 0, 0,0xC6,0x2B},
{113, 0, 0,0x46,0x2C},
{114, 0, 0,0xC5,0x2D},
{115, 0, 0,0x45,0x2E},
{114, 0, 0,0xC5,0x00},
{ 78, 0, 3,0x8A,0x00},
{ 77, 4, 3,0x87,0x00},
{211, 0, 1,0x44,0x00},
{212, 0, 1,0x44,0x00},
{213, 0, 1,0x44,0x00},
{214, 0, 1,0x47,0x00},
{215, 0, 7,0x4B,0x00},
{255, 0, 0,0x00,0x00},
{ 79, 4, 7,0x49,0x00},
{130, 0, 0,0x44,0x00},
{131, 0, 0,0x44,0x00},
{132, 0, 2,0x44,0x00},
{255, 0, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{192, 0, 0,0x00,0x00},
{193, 0, 1,0x00,0x00},
{194, 0, 0,0x80,0x00},
{195, 0, 0,0x00,0x00},
{196, -1, 0,0x00,0x00},
{197, -1, 0,0x00,0x00},
{198, -1, 0,0x00,0x00},
{199, -4, 0,0x00,0x00},
{200, -4, 0,0x80,0x00},
{201, -4, 0,0x00,0x00},
{202, -4, 0,0x00,0x00},
{203, -4, 0,0x00,0x00},
{204, -4, 0,0x00,0x00},
{205, -5, 0,0x00,0x00},
{206, -5, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{207, 0, 1,0x46,0x00},
{208, 0, 1,0xC6,0x00},
{209, 0, 1,0xC8,0x00},
{210, 0, 1,0x4A,0x00},
{255, 0, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{255, 0, 0,0x00,0x00},
{ 52, 0, 0,0x80,0x00},
{ 53, 0, 0,0x00,0x00},
{ 54, 0, 0,0x00,0x00},
{ 55, 0, 0,0x00,0x00},
{ 56, 0, 0,0x80,0x00},
{ 57, 0, 0,0x00,0x00},
{ 58, 0, 0,0x00,0x00},
{ 59, 0, 0,0x00,0x00},
{ 60, 0, 0,0x80,0x00},
{ 61, 0, 0,0x00,0x00},
{ 62, 0, 0,0x80,0x00},
{ 63, 0, 0,0x00,0x00},
{160, 1, 1,0xC3,0x23},
{161, 0, 1,0x49,0x24},
{162, 0, 1,0xC3,0x25},
{163, 0, 1,0x49,0x26},
{164, 0, 1,0xC3,0x27},
{165, 1, 1,0x49,0x28},
{166, 1, 1,0x43,0x29},
{167, 1, 1,0xC9,0x2A},
{168, 4, 1,0xC6,0x00},
{169, 3, 1,0xCA,0x00},
{170, 1, 1,0x43,0x00},
{171, 1, 1,0xC8,0x00},
};
#define KID_SEQTBL_LEN 2310 #define KID_SEQTBL_LEN 2310
static const uint8_t kid_seqtbl[KID_SEQTBL_LEN] = {
0xF9,0x01,0xFF,0x81,0x19,0xF9,0x01,0x01,0x02,0x03,0x04,0xFB, /* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */
0x08,0x05,0xFB,0x03,0x06,0xFB,0x03,0x07,0xFB,0x05,0x08,0xFB, #define KID_BIN_FRAMES_OFF 0
0x01,0xF2,0x01,0x09,0xFB,0x02,0x0A,0xFB,0x04,0x0B,0xFB,0x05, #define KID_BIN_SEQTBL_OFF 1205
0x0C,0xFB,0x02,0xF2,0x01,0x0D,0xFB,0x03,0x0E,0xFB,0x04,0xFF, /* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс
0x81,0x19,0xF9,0x00,0x0F,0xFF,0xA0,0x19,0xF9,0x01,0xA6,0xFF, * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */
0xA8,0x19,0xF9,0x05,0xFB,0x0A,0xB1,0xB1,0xFB,0xF9,0xFA,0xFE, #define KID_NGFRAMES 41
0xB2,0xFB,0x05,0xFA,0x02,0xA6,0xFB,0xFF,0xFF,0xD2,0x19,0xFF, #define KID_BIN_GFRAMES_OFF 3515
0xD2,0x19,0xF9,0x01,0xFB,0x02,0xCF,0xD0,0xFB,0x02,0xD1,0xFB,
0x02,0xD2,0xFB,0x03,0xF9,0x01,0xF2,0x00,0x9E,0xAA,0xAB,0xFF,
0xD8,0x19,0xF9,0x05,0xF8,0xFF,0x00,0xAC,0xFB,0xFF,0xFA,0x01,
0xAD,0xFB,0xFF,0xAE,0xFB,0xFF,0xFA,0x02,0xFB,0xFE,0xFA,0x01,
0xFB,0xFB,0xFA,0xFC,0xFF,0x4D,0x1A,0xF9,0x01,0xF8,0x01,0x00,
0x9B,0xFB,0x02,0xA5,0xFB,0xFE,0xFF,0xD2,0x19,0xF9,0x01,0xF8,
0xFF,0x00,0x9B,0x9C,0x9D,0x9E,0xFF,0x2E,0x1A,0xF9,0x01,0xF8,
0x01,0x00,0xFB,0x02,0xA3,0xFB,0x04,0xA4,0xA5,0xFF,0xD2,0x19,
0xF9,0x01,0xF8,0x01,0x00,0xFB,0x06,0xA4,0xA5,0xFF,0xD2,0x19,
0xF9,0x01,0xF8,0xFF,0x00,0xFB,0xFD,0xA0,0xFB,0xFE,0x9D,0xFF,
0xD2,0x19,0xF9,0x01,0xF8,0xFF,0x00,0xA8,0xF9,0x01,0x97,0xF9,
0x01,0x98,0x99,0x9A,0xF9,0x05,0x9B,0xF9,0x01,0x9C,0x9D,0xFF,
0xD2,0x19,0xF9,0x01,0xA7,0xFF,0x4A,0x1A,0xA2,0xFF,0x45,0x1A,
0xA9,0x96,0xFF,0xD2,0x19,0x9F,0xA0,0xFF,0x5F,0x1A,0xF9,0x01,
0xF4,0xFF,0xD2,0x19,0xF9,0x05,0xFB,0xF8,0xFF,0xD2,0x19,0xF9,
0x05,0xA0,0x9D,0xFF,0xD2,0x19,0xF9,0x07,0xFB,0xF8,0xFF,0x39,
0x1B,0xF9,0x05,0xFE,0xFB,0x05,0xFF,0x2E,0x1A,0xF9,0x05,0xFE,
0xFB,0x12,0xFF,0xA6,0x19,0xF9,0x01,0x10,0x11,0xFB,0x02,0x12,
0xFB,0x02,0x13,0xFB,0x02,0x14,0xFB,0x02,0x15,0xFB,0x02,0x16,
0xFB,0x07,0x17,0xFB,0x09,0x18,0xFB,0x05,0xFA,0xFA,0x19,0xFB,
0x01,0xFA,0x06,0x1A,0xFB,0x04,0xF4,0xF2,0x01,0x1B,0xFB,0xFD,
0x1C,0xFB,0x05,0x1D,0xF2,0x01,0x1E,0x1F,0x20,0x21,0xFB,0x01,
0xFF,0xA0,0x19,0xF9,0x01,0xF2,0x01,0x22,0xFB,0x05,0x23,0xFB,
0x06,0x24,0xFB,0x03,0x25,0xFB,0x05,0xF2,0x01,0x26,0xFB,0x07,
0x27,0xFB,0x0C,0xFA,0xFD,0x28,0xFB,0x08,0xFA,0xF7,0x29,0xFB,
0x08,0xFA,0xFE,0x2A,0xFB,0x04,0xFA,0x0B,0x2B,0xFB,0x04,0xFA,
0x03,0x2C,0xFB,0x05,0xF4,0xF2,0x01,0xFF,0x81,0x19,0xF9,0x01,
0xFB,0x01,0x6B,0xFB,0x02,0xFB,0x02,0x6C,0xFB,0x02,0x6D,0xFB,
0x02,0x6D,0xFB,0x02,0x6D,0xFF,0x16,0x1B,0x00,0xF9,0x01,0xFB,
0x01,0x6E,0x6F,0xFB,0x02,0x70,0xFB,0x02,0x6C,0xFB,0x02,0x6D,
0xFF,0x29,0x1B,0xF9,0x07,0xFE,0xFB,0x06,0x2D,0xFB,0x01,0x2E,
0xFF,0xC4,0x19,0xF9,0x07,0xFE,0xFB,0x06,0x2D,0xFB,0x01,0x2E,
0xFB,0x02,0x2F,0xFB,0xFF,0x30,0xFB,0x01,0x31,0xFB,0xFE,0x32,
0x33,0x34,0xFF,0xA0,0x19,0xF9,0x01,0xFB,0xFF,0xFF,0x75,0x19,
0xF9,0x01,0xFB,0x01,0x35,0xFB,0x01,0xF2,0x01,0x36,0xFB,0x08,
0x37,0xF2,0x01,0x38,0xFB,0x07,0x39,0xFB,0x03,0x3A,0xFB,0x01,
0x3B,0x3C,0xFB,0x02,0x3D,0xFB,0xFF,0x3E,0x3F,0x40,0xFB,0xFF,
0x41,0xFB,0xF2,0xFE,0xFF,0x95,0x19,0xF9,0x03,0xFA,0xFF,0x66,
0xFB,0xFE,0xFA,0x06,0x67,0xFB,0xFE,0xFA,0x09,0x68,0xFB,0xFF,
0xFA,0x0C,0x69,0xFB,0xFD,0xF8,0x00,0x0F,0xFF,0x49,0x1D,0xF9,
0x03,0xFA,0xFF,0xFB,0xFE,0x66,0xFB,0xFD,0xFA,0x06,0x67,0xFB,
0xFD,0xFA,0x09,0x68,0xFB,0xFE,0xFA,0x0C,0x69,0xFB,0xFD,0xF8,
0x00,0x0F,0xFF,0x49,0x1D,0xF9,0x03,0xFB,0x01,0xFA,0xFF,0x66,
0xFB,0x02,0xFA,0x06,0x67,0xFB,0xFF,0xFA,0x09,0x68,0xFA,0x0C,
0x69,0xFB,0xFE,0xF8,0x01,0x0F,0xFF,0x49,0x1D,0xF9,0x03,0xFB,
0x01,0xFA,0x03,0xF7,0x06,0x1C,0x66,0xFB,0x02,0xFA,0x06,0x67,
0xFB,0xFF,0xFA,0x09,0x68,0xFA,0x0C,0x69,0xFB,0xFE,0xF8,0x01,
0x0F,0xFF,0x49,0x1D,0xFB,0xFF,0xFA,0xFD,0xFF,0xE4,0x1B,0xFB,
0x01,0xFF,0xDB,0x1B,0x66,0xFB,0x02,0xFA,0x03,0x67,0xFB,0xFF,
0xFA,0x04,0x68,0xFA,0x05,0x69,0xFB,0xFE,0xF8,0x01,0x06,0xFF,
0x49,0x1D,0xF9,0x03,0xFB,0x01,0xFA,0x03,0x66,0xFB,0x02,0xFA,
0x06,0x67,0xFB,0x01,0xFA,0x09,0x68,0xFB,0x02,0xFA,0x0C,0x69,
0xF8,0x02,0x0F,0xFF,0x49,0x1D,0xF9,0x03,0xFB,0x01,0xFA,0x03,
0x66,0xFB,0x03,0xFA,0x06,0x67,0xFB,0x02,0xFA,0x09,0x68,0xFB,
0x03,0xFA,0x0C,0x69,0xF8,0x03,0x0F,0xFF,0x49,0x1D,0xF9,0x01,
0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0xF9,
0x02,0x4E,0x4F,0x50,0xFF,0xA1,0x1C,0xF9,0x01,0x43,0x44,0x45,
0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0xF9,0x02,0xFB,0x01,
0x4E,0xFB,0x02,0x4F,0xFB,0x01,0x50,0xFF,0xA1,0x1C,0xF9,0x01,
0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0xFB,0xFF,
0x4D,0xF9,0x02,0xFB,0xFE,0x4E,0xFB,0xFF,0x4F,0xFB,0xFF,0x50,
0xFF,0xA1,0x1C,0xF9,0x02,0x5B,0x5A,0x59,0x58,0x57,0x57,0x57,
0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63,
0x61,0x60,0x5F,0x5E,0x5D,0x5C,0x5B,0x5A,0x59,0x58,0x57,0x58,
0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x5F,0x5E,0x5D,0x5C,
0xFF,0x25,0x1D,0xF9,0x06,0x5C,0x5D,0x5D,0x5C,0x5C,0x5B,0xFF,
0xD8,0x1C,0x87,0x88,0x89,0x89,0x8A,0x8A,0x8A,0x8A,0x89,0x88,
0x87,0xFB,0xF9,0xFF,0x25,0x1D,0xF9,0x01,0x94,0x91,0x90,0x8F,
0x8E,0x8D,0xFB,0xFB,0xFA,0x3F,0xFC,0xF9,0x03,0x8C,0x8A,0x88,
0x5B,0xF9,0x02,0xFF,0xA4,0x1C,0xF9,0x01,0x87,0x88,0x89,0x8A,
0x8B,0x8C,0xFB,0x05,0xFA,0xC1,0xFD,0x8D,0x8E,0x8F,0x90,0x91,
0x92,0x93,0x94,0xF9,0x05,0x95,0xF9,0x01,0x76,0x77,0xFB,0x01,
0xFF,0xA0,0x19,0x51,0x52,0xF9,0x05,0x53,0xF9,0x01,0xF4,0xF2,
0x00,0x54,0x55,0xFB,0x03,0xFF,0xA0,0x19,0xF9,0x03,0x51,0xFA,
0x06,0x51,0xFA,0x09,0x51,0xFA,0x0C,0xFB,0x02,0xF8,0x00,0x0C,
0xFF,0x49,0x1D,0xF9,0x04,0x6A,0xFF,0x4B,0x1D,0xF9,0x01,0x35,
0xFB,0x02,0xF2,0x01,0x36,0xFB,0x07,0x37,0xF2,0x01,0x38,0xFB,
0x02,0x31,0xFB,0xFE,0x32,0x33,0x34,0xFF,0xA0,0x19,0xF9,0x01,
0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,
0xF9,0x00,0xF5,0x4F,0xFF,0x25,0x1D,0xF9,0x01,0x43,0x44,0x45,
0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0xFA,0xFC,
0x4F,0xFA,0xFE,0x4F,0x4F,0xFA,0x02,0x4F,0xFA,0x04,0xFF,0x25,
0x1D,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0xFA,
0xFF,0x4D,0xFA,0xFD,0x4E,0xFA,0xFC,0x4F,0xFA,0xF6,0x4F,0xFA,
0xF7,0x4F,0xFA,0xF8,0x4F,0xFA,0xF9,0x4F,0xFA,0xFA,0x4F,0xFA,
0xFB,0x4F,0xFA,0xFC,0x4F,0xFA,0xFD,0x4F,0xFA,0xFE,0x4F,0xFA,
0xFE,0x4F,0xFA,0xFF,0x4F,0xFA,0xFF,0x4F,0xFA,0xFF,0x4F,0x4F,
0x4F,0x4F,0xFA,0x01,0x4F,0xFA,0x01,0x4F,0xFA,0x02,0x4F,0xFA,
0x02,0x4F,0xFA,0x03,0x4F,0xFA,0x04,0x4F,0xFA,0x05,0x4F,0xFA,
0x06,0x4F,0xF8,0x00,0x06,0xFF,0x49,0x1D,0xF9,0x03,0x50,0xFF,
0xA1,0x1C,0xF9,0x05,0xFB,0xFC,0x32,0x33,0x34,0xFF,0xA0,0x19,
0xF9,0x05,0xFB,0x01,0xFA,0x03,0xF7,0x25,0x1E,0x66,0xFB,0x02,
0xFA,0x06,0x67,0xFB,0xFF,0xFA,0x09,0x68,0xFA,0x0C,0x69,0xFB,
0xFE,0xF8,0x00,0x0F,0xFF,0x49,0x1D,0x66,0xFB,0x02,0xFA,0x03,
0x67,0xFB,0xFF,0xFA,0x04,0x68,0xFA,0x05,0x69,0xFB,0xFE,0xF8,
0x00,0x06,0xFF,0x49,0x1D,0xF9,0x05,0xFB,0xFF,0xFA,0xFC,0x66,
0xFB,0xFF,0xFA,0x03,0xFB,0xFD,0xFA,0x01,0xF4,0xFB,0x01,0xF2,
0x01,0x6B,0xFB,0x02,0x6C,0xF2,0x01,0x6D,0xFF,0xB3,0x1F,0x79,
0xFB,0x01,0x7A,0x7B,0xFB,0x02,0x7C,0xFB,0x04,0x7D,0xFB,0x03,
0x7E,0xFB,0xFC,0x56,0xF2,0x01,0xF4,0xFB,0xFC,0x74,0xFB,0xFE,
0x75,0x76,0x77,0xFF,0xA0,0x19,0xFB,0xFB,0xFF,0xA0,0x19,0xF9,
0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB,
0x04,0x7D,0xFB,0x03,0x7E,0xFB,0xFF,0xFB,0x03,0x7F,0x80,0x81,
0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,
0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB,0x04,0x7D,0xFB,0x03,0x7E,
0xFB,0xFF,0xFB,0x02,0x7F,0x80,0x81,0x82,0x83,0x84,0xFF,0xA0,
0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,
0x7C,0xFB,0x04,0x7D,0xFB,0x03,0x7E,0xFB,0xFF,0xFB,0x01,0x7F,
0x80,0x81,0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,
0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB,0x04,0x7D,0xFB,
0x03,0x7E,0xFB,0xFF,0x7F,0x80,0x81,0x82,0x83,0x84,0xFF,0xA0,
0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,
0x7C,0xFB,0x04,0x7D,0xFB,0x03,0x7E,0xFB,0xFE,0x80,0x81,0x82,
0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFF,0xFC,0x1E,0xF9,
0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,0x7C,0xFB,
0x04,0x7D,0xFB,0xFF,0x7F,0x80,0x81,0x82,0x83,0x84,0xFF,0xA0,
0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x03,
0x7C,0xFB,0x02,0x81,0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,
0x79,0xFB,0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x02,0x7C,0xFB,0x02,
0x81,0x82,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,0x01,
0x7A,0xFB,0x01,0x7B,0xFB,0x02,0x7C,0xFB,0x01,0x81,0x82,0x83,
0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,
0x7B,0xFB,0x02,0x83,0x84,0xFF,0xA0,0x19,0xF9,0x01,0x79,0xFB,
0x01,0x7A,0xFB,0x01,0x7B,0xFB,0x01,0x83,0x84,0xFF,0xA0,0x19,
0xF9,0x01,0x79,0xFB,0x01,0x7A,0xFB,0x01,0x84,0xFF,0xA0,0x19,
0xF9,0x01,0x79,0xFB,0x01,0x84,0xFF,0xA0,0x19,0xF9,0x01,0xFB,
0x01,0x6B,0xFB,0x02,0x6C,0x6D,0xFF,0xAF,0x1F,0xF9,0x05,0xFB,
0x01,0x6E,0x6F,0xFB,0x02,0x70,0x71,0xFB,0x01,0x72,0x73,0x74,
0xFB,0xFC,0x75,0x76,0x77,0xFF,0xA0,0x19,0xF9,0x01,0xF3,0x01,
0xE5,0xE5,0xE5,0xE5,0xE5,0xE5,0xE6,0xE7,0xE8,0xFF,0xDA,0x1F,
0xF9,0x01,0xFB,0xFB,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,
0x85,0x85,0x86,0x86,0x86,0x30,0xFB,0x01,0x31,0xFB,0xFE,0xF9,
0x05,0x32,0xF9,0x01,0x33,0x34,0xFF,0xA0,0x19,0xF9,0x01,0xFB,
0xFB,0xEA,0xEC,0xEE,0xF0,0x86,0xFB,0xFF,0xFF,0xA0,0x19,0xF9,
0x01,0xFB,0x04,0xBF,0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xF2,0x03,
0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCD,0xCD,0xF3,0x01,
0xCD,0xCD,0xC9,0xC6,0xFB,0xFC,0xFF,0xA0,0x19,0xF9,0x05,0xF4,
0xFB,0x01,0x6B,0xFB,0x02,0x6C,0xF9,0x01,0x6D,0xFF,0x36,0x20,
0xF9,0x01,0xFA,0xFE,0xFB,0x01,0x6B,0xFB,0x02,0x6C,0x6D,0xFB,
0x01,0x6E,0x6F,0xFB,0x02,0x70,0x71,0xFB,0x01,0xFA,0x01,0x72,
0xFA,0x01,0x73,0xFB,0xFE,0xFF,0x78,0x19,0xF9,0x05,0xF4,0xFA,
0xFE,0xFB,0x01,0xFB,0x02,0x6C,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,
0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,
0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0x6D,0xFB,
0x01,0x6E,0x6E,0x6E,0x6F,0xFB,0x02,0x70,0x71,0xFB,0x01,0xFA,
0x01,0x72,0xFA,0x01,0x73,0x74,0xFB,0xFC,0x75,0x76,0x77,0xFF,
0xA0,0x19,0xF9,0x05,0xF4,0xFA,0xFE,0xFB,0x03,0xB9,0xF6,0xB9,
0xFF,0xA5,0x20,0xF9,0x05,0xFF,0xAE,0x20,0xF9,0x01,0xF6,0xB3,
0xB4,0xB5,0xB6,0xFB,0x01,0xB7,0xFB,0xFC,0xB9,0xFF,0xBA,0x20,
0xF9,0x01,0xF4,0xFB,0x04,0xB1,0xF6,0xB1,0xFF,0xC5,0x20,0xF9,
0x01,0xB2,0xF6,0xB2,0xFF,0xCD,0x20,0xFF,0x5A,0x20,0xF8,0x00,
0x00,0xF9,0x04,0xB9,0xFF,0xD9,0x20,0xF9,0x05,0xFB,0xFB,0xFA,
0xFF,0xF2,0x01,0xD9,0xDA,0xDB,0xFB,0x01,0xDC,0xFB,0xFC,0xFA,
0xFD,0xF2,0x01,0xDD,0xFB,0xFC,0xFA,0xFE,0xDE,0xFB,0xFE,0xFA,
0xFD,0xDF,0xFB,0xFD,0xFA,0xF8,0xF2,0x04,0xF2,0x01,0xE0,0xFB,
0xFF,0xFA,0xFF,0xE1,0xFB,0xFD,0xFA,0xFC,0xE2,0xFB,0xFF,0xFA,
0xFB,0xF2,0x01,0xE3,0xFB,0xFE,0xFA,0xFF,0xE4,0x00,0xF2,0x01,
0x00,0x00,0x00,0xF2,0x01,0x00,0x00,0x00,0xF2,0x01,0x00,0x00,
0x00,0xF2,0x01,0xF1,0x00,0xFF,0x2E,0x21,0x36,0xFF,0x32,0x21,
0x55,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x43,0x44,
0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x53,0x54,0x4C,0xFF,0x4B,
0x21,0xFB,0x01,0x30,0xFB,0x02,0x31,0xFB,0x06,0x32,0xFB,0x01,
0x33,0xFB,0xFF,0x34,0xFB,0x01,0x35,0xFB,0x01,0xFF,0x51,0x21,
0xFB,0x01,0x37,0x38,0xFF,0x32,0x21,0x4D,0x4E,0x4F,0x50,0x51,
0x52,0xFB,0x01,0x36,0x36,0x36,0x36,0x36,0x36,0x39,0x3A,0x3B,
0x3C,0x3D,0xFB,0x02,0x3E,0xFB,0xFF,0x3F,0xFB,0xFD,0x40,0x41,
0xFB,0xFF,0x42,0xFE,0xFB,0x10,0xFB,0x03,0xFF,0x54,0x21,0x0B,
0xFF,0x95,0x21,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFE,
0xFB,0x08,0x0B,0xFF,0x95,0x21,0xFE,0xFB,0x0B,0x0C,0xFB,0x01,
0x0D,0xFB,0x01,0x0E,0xFB,0x03,0x0F,0xFB,0x01,0x10,0x11,0xFF,
0xB8,0x21,0x13,0xFF,0xBC,0x21,0x14,0xFF,0xC0,0x21,0x15,0xFB,
0x01,0x16,0x17,0x18,0xFB,0x01,0x19,0xFB,0xFD,0x1A,0xFB,0xFE,
0x1B,0xFB,0xFC,0x1C,0xFB,0xFD,0x1D,0xFB,0xFE,0x1E,0xFB,0xFD,
0x1F,0xFB,0xFF,0x20,0x21,0xFF,0xE2,0x21,0x25,0xFF,0xE6,0x21,
0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0xFE,
0xFB,0x0C,0x0B,0xFF,0xF8,0x21,0x0B,0x0B,0xFE,0xFB,0x0D,0x2F,
0x2E,0x2D,0x2C,0x2B,0x2A,0x29,0x28,0x27,0x26,0x25,0x24,0x24,
0x24,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x23,
0x23,0x24,0x24,0x24,0x23,0x23,0x23,0x22,0x22,0x22,0x22,0x22,
0x22,0x22,0x23,0x23,0x24,0x24,0x24,0x23,0x23,0x23,0x22,0x22,
0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x23,0x23,0x23,0x24,0xFF,
0x3C,0x22,0x01,0x12,0xFF,0x41,0x22,0xF9,0x01,0xBA,0xFB,0x05,
0xBA,0xFB,0x03,0xBB,0xFB,0x04,0xFF,0x47,0x22,0xBA,0xFF,0x53,
0x22,0xBC,0xFF,0x57,0x22,0xF9,0x00,0xBA,0xBA,0xBA,0xBC,0xBC,
0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xFE,0xFB,0x08,0xFF,0x47,0x22,
0xBA,0xBA,0xBC,0xFF,0x70,0x22,
};
#define KID_NSEQ 115 #define KID_NSEQ 115
static const uint16_t kid_seq_off[KID_NSEQ] = { static const uint16_t kid_seq_off[KID_NSEQ] = {
+277 -95
View File
@@ -14,6 +14,8 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include "pop_bg.h" #include "pop_bg.h"
#include "pop_state.h"
#include "pop_geom.h"
#include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */ #include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */
#include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев (см. ниже) */ #include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев (см. ниже) */
@@ -24,22 +26,25 @@ static atlas_t fore_at;
static atlas_t pot_at; /* chtab_1: зелья (склянка + пузырёк) */ static atlas_t pot_at; /* chtab_1: зелья (склянка + пузырёк) */
static const char *const ENV_ATL[5] = { static const char *const ENV_ATL[5] = {
"pop_env0.atl", "pop_env1.atl", "pop_env2.atl", /* Ресурсы разложены по каталогам образа (8.3, как в DSS): BG — фон,
"pop_env3.atl", "pop_env4.atl" * KID — персонаж, GUARD — стражи, LEVELS — уровни. Иначе корень диска
* зарастает десятками .atl. */
"BG\\pop_env0.atl", "BG\\pop_env1.atl", "BG\\pop_env2.atl",
"BG\\pop_env3.atl", "BG\\pop_env4.atl"
}; };
int pop_bg_load(void) int pop_bg_load(void) __banked
{ {
int i; int i;
for (i = 0; i < 5; i++) for (i = 0; i < 5; i++)
if (atlas_load(&env[i], ENV_ATL[i]) != 0) return -1; if (atlas_load(&env[i], ENV_ATL[i]) != 0) return -1;
if (atlas_load(&wall_at, "pop_wall.atl") != 0) return -1; if (atlas_load(&wall_at, "BG\\pop_wall.atl") != 0) return -1;
if (atlas_load(&fore_at, "pop_fore.atl") != 0) return -1; if (atlas_load(&fore_at, "BG\\pop_fore.atl") != 0) return -1;
if (atlas_load(&pot_at, "pop_pot.atl") != 0) return -1; if (atlas_load(&pot_at, "BG\\pop_pot.atl") != 0) return -1;
return 0; return 0;
} }
void pop_bg_free(void) void pop_bg_free(void) __banked
{ {
int i; int i;
for (i = 0; i < 5; i++) for (i = 0; i < 5; i++)
@@ -58,33 +63,73 @@ static int g_clip_top;
/* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x, /* Блит ленты idx атласа a: НИЗ спрайта на ybottom, левый край x,
* прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0). * прозрачно (0xFF). w/h читаются из getimage-заголовка ленты (в W0).
* При g_clip_top рисуем только часть кадра ниже границы (gfx_blit_part). */ * При g_clip_top рисуем только часть кадра ниже границы (gfx_blit_part). */
/* ОКНО FORE-СЛОЯ (экранные координаты, полуинтервал). Пока оно включено,
* blit_b рисует только ту часть куска, что попадает в окно.
*
* Зачем: fore-слой существует РОВНО для того, чтобы вернуть куски тайлов
* поверх нарисованного персонажа — за пределами его прямоугольника в
* видеопамяти и так лежит правильный фон. А рисовал он тайлы ЦЕЛИКОМ:
* стена (код 20) — блок 64×63 = 4032 пикселя, и таких кусков за проход
* до девяти. Замер в MAME 2026-07-29 (комната 3, Kid + страж): проход
* поверх Kid — 402 816 тактов, поверх стража — 431 964, вместе 78 % всей
* работы кадра при бюджете 430 000 тактов на кадр. С окном большинство
* кусков отсекается целиком, остальные режутся до пересечения. */
static int fclip_x0, fclip_y0, fclip_x1, fclip_y1;
static uint8_t fclip_on;
void pop_fore_set_clip(int x, int y, int w, int h) __banked
{
fclip_x0 = x; fclip_y0 = y;
fclip_x1 = x + w; fclip_y1 = y + h;
}
static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom) static void blit_b(atlas_t *a, uint8_t idx, int x, int ybottom)
{ {
const uint8_t *img; const uint8_t *img;
uint16_t w, h; uint16_t w, h;
int top; int top, dx, dy, dw, dh, sx = 0, sy = 0;
if (idx >= a->count) if (idx >= a->count)
return; return;
/* Грубый отсев ДО маппинга страницы: реальные w/h лежат в заголовке
* ленты, то есть в EMM-странице, и чтобы их прочитать, надо сделать
* gfx_w0_map. Берём заведомо больший габарит (кадры фона не крупнее
* 64×64) и, если он не задевает окно, уходим не заплатив ни за
* atlas_image, ни за map/unmap. */
if (fclip_on &&
(x >= fclip_x1 || x + 64 <= fclip_x0 ||
ybottom + POP_YOFF + 1 <= fclip_y0 ||
ybottom + POP_YOFF - 64 >= fclip_y1))
return;
img = (const uint8_t *)atlas_image(a, idx); img = (const uint8_t *)atlas_image(a, idx);
gfx_w0_map(a->page); gfx_w0_map(a->page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */ top = ybottom - (int)h + 1 + POP_YOFF; /* +YOFF: центрирование */
if (w && h) { dx = x; dy = top; dw = (int)w; dh = (int)h;
if (g_clip_top && top < g_clip_top) { if (g_clip_top && dy < g_clip_top) { /* полоса у потолка */
int sy = g_clip_top - top; /* пропустить верхние строки */ sy = g_clip_top - dy; dy = g_clip_top; dh -= sy;
if (sy < (int)h) }
gfx_blit_part(x, g_clip_top, img, 0, sy, (int)w, (int)h - sy); if (fclip_on) { /* окно fore-слоя */
} else if (x >= 0 && top >= 0 && w < 256 && h < 256 && if (dx < fclip_x0) { int d = fclip_x0 - dx; sx += d; dx += d; dw -= d; }
x + (int)w <= 320 && top + (int)h <= 256) { if (dy < fclip_y0) { int d = fclip_y0 - dy; sy += d; dy += d; dh -= d; }
/* Спрайт целиком на экране — линейное ядро без клипа: ~2.9× if (dx + dw > fclip_x1) dw = fclip_x1 - dx;
* быстрее (замер: 4617 против 13288 тактов на спрайт 32×3; if (dy + dh > fclip_y1) dh = fclip_y1 - dy;
* общее ядро gfx_blit платит за клип/16-бит/split, а не за }
* пиксели). W3-скобку ставит сама libbgi: из кода, собранного if (w && h && dw > 0 && dh > 0) {
* с --w3, её вызывать нельзя (окно W3 уходит под видеобанк). */ /* Целиком на экране — линейное ядро без клипа: ~2.9× быстрее
gfx_blit_noclip(x, top, img); * (замер: 4617 против 13288 тактов на спрайт 32×3; общее ядро
* gfx_blit платит за клип/16-бит/split, а не за пиксели).
* W3-скобку ставит сама libbgi: из кода, собранного с --w3, её
* вызывать нельзя (окно W3 уходит под видеобанк). */
if (dx >= 0 && dy >= 0 && dw < 256 && dh < 256 &&
dx + dw <= 320 && dy + dh <= 256 && w < 256) {
if (sx == 0 && sy == 0 && dw == (int)w && dh == (int)h)
gfx_blit_noclip(dx, dy, img);
else
gfx_blit_part_noclip(dx, dy, img, (uint8_t)sx, (uint8_t)sy,
(uint8_t)dw, (uint8_t)dh);
} else { } else {
gfx_blit(x, top, img); /* край экрана — с клипом */ gfx_blit_part(dx, dy, img, sx, sy, dw, dh); /* край экрана — с клипом */
} }
} }
gfx_w0_unmap(); gfx_w0_unmap();
@@ -125,7 +170,7 @@ static void ov_mark(int x, int y, int w, int h)
ov_pending = 3; /* обе страницы */ ov_pending = 3; /* обе страницы */
} }
void pop_fore_heal(void) void pop_fore_heal(void) __banked
{ {
uint8_t pg = gfx_get_draw_page() & 1; uint8_t pg = gfx_get_draw_page() & 1;
if (!(ov_pending & (1 << pg))) return; if (!(ov_pending & (1 << pg))) return;
@@ -249,12 +294,12 @@ static uint8_t g_room;
* floorright, когда тайл нижнего ряда стал видимым (empty после падения * floorright, когда тайл нижнего ряда стал видимым (empty после падения
* loose): «стена вниз» = топрайт тайла room_B(0, col-1). 0 — нет комнаты * loose): «стена вниз» = топрайт тайла room_B(0, col-1). 0 — нет комнаты
* снизу (кромка уровня = стена). */ * снизу (кромка уровня = стена). */
void pop_room_set_below(const uint8_t *below_row0_fg) { g_below = below_row0_fg; } void pop_room_set_below(const uint8_t *below_row0_fg) __banked { g_below = below_row0_fg; }
/* Задать ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки /* Задать ряд 2 (fg-коды + модификаторы) комнаты СВЕРХУ — для полосы кладки
* у потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom * у потолка (seg008 draw_room: доп. верхний ряд из room_A, draw_tile_aboveroom
* при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */ * при draw_main_y=-1). 0 — нет комнаты сверху (кромка уровня → пол). */
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod) void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod) __banked
{ g_above = above_row2_fg; g_abovemod = above_row2_mod; } { g_above = above_row2_fg; g_abovemod = above_row2_mod; }
static uint8_t tile_code(int row, int col) static uint8_t tile_code(int row, int col)
@@ -311,11 +356,7 @@ static uint8_t wall_modifier(int row, int col)
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- */ /* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- */
static unsigned long rnd_seed; static unsigned long rnd_seed;
static uint16_t prandom(uint16_t maxv) #define prandom(maxv) pop_prandom(&rnd_seed, (maxv))
{
rnd_seed = rnd_seed * 214013UL + 2531011UL;
return (uint16_t)((uint16_t)(rnd_seed >> 16) % (uint16_t)(maxv + 1));
}
/* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */ /* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */
static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col) static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col)
@@ -339,64 +380,134 @@ static void draw_right_mark(uint16_t dv, int arg1, int row, int col)
} }
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */ /* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
static void wall_pattern(int row, int col, int which_part) /* КЭШ РАСКЛАДКИ КЛАДКИ. Серия prandom для тайла зависит только от
{ * (комната, ряд, колонка) — пока мы в комнате, это КОНСТАНТА. А
int xh = COL_XH[col]; * wall_pattern зовётся из fore-слоя КАЖДЫЙ кадр, и на Z80 это самая
int dby = 63 * row + 65; * дорогая вещь в кадре: замер в MAME 2026-07-29 (комната 3) — 18 вызовов
unsigned long saved = rnd_seed; * pop_prandom за один fore-проход, ~10 700 тактов каждый (32-битный LCG:
uint16_t mid_div, mid_off, bot_div, bot_off; * __mullong ~8 000 + __moduint), то есть 193 К из 341 К всего прохода при
uint8_t bg; * бюджете кадра ~430 К. Кэшируем не сид, а ГОТОВЫЕ РЕШЕНИЯ (что рисовать
* и с каким смещением) — тогда в кадре не остаётся ни одного prandom.
*
* Порядок вызовов prandom воспроизведён один в один, включая то, что
* значение метки берётся ТОЛЬКО когда сработало её условие: иначе
* последовательность разъедется и кладка станет другой. which_part=0
* использует префикс той же серии, поэтому кэш общий.
*
* Упаковка: wp_a = mid_div | mid_off<<1 | bot_div<<4 | bot_off<<5;
* wp_b = rndblk | right<<1 | right_val<<2 | left<<4 | left_val<<5.
* Индекс — (row+1)*10+col, ряды 1..2. */
#define WP_N 40
static uint8_t wp_a[WP_N], wp_b[WP_N], wp_ok[WP_N];
static void wall_pattern_reset(void) /* смена комнаты — сид другой */
{
uint8_t i;
for (i = 0; i < WP_N; i++) wp_ok[i] = 0;
}
/* Выходы wall_rnd — в файловых статиках: возвращать девять значений
* через указатели на Z80 дороже самой функции. */
static uint8_t wr_mid_div, wr_mid_off, wr_bot_div, wr_bot_off;
static uint8_t wr_rndblk, wr_right, wr_rightv, wr_left, wr_leftv;
static void wall_rnd(int row, int col)
{
/* Кэшируем только штатную сетку: колонка вне 0..9 дала бы индекс
* соседнего РЯДА и чужую раскладку (тихий баг вместо промаха). */
int idx = (col >= 0 && col < 10) ? (row + 1) * 10 + col : -1;
uint8_t a, b, bg;
unsigned long saved;
if (idx >= 0 && idx < WP_N && wp_ok[idx]) {
a = wp_a[idx]; b = wp_b[idx];
wr_mid_div = (uint8_t)(a & 1); wr_mid_off = (uint8_t)((a >> 1) & 7);
wr_bot_div = (uint8_t)((a >> 4) & 1); wr_bot_off = (uint8_t)((a >> 5) & 7);
wr_rndblk = (uint8_t)(b & 1);
wr_right = (uint8_t)((b >> 1) & 1); wr_rightv = (uint8_t)((b >> 2) & 3);
wr_left = (uint8_t)((b >> 4) & 1); wr_leftv = (uint8_t)((b >> 5) & 7);
return;
}
saved = rnd_seed;
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху /* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */ * (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
rnd_seed = (unsigned long)(g_room + TBL_LINE[row < 0 ? 2 : row] + col); rnd_seed = (unsigned long)(g_room + TBL_LINE[row < 0 ? 2 : row] + col);
prandom(1); /* сброс */ prandom(1); /* сброс */
mid_div = prandom(1); wr_mid_div = (uint8_t)prandom(1);
mid_off = prandom(4); wr_mid_off = (uint8_t)prandom(4);
bot_div = prandom(1); wr_bot_div = (uint8_t)prandom(1);
bot_off = prandom(4); wr_bot_off = (uint8_t)prandom(4);
wr_rndblk = wr_right = wr_left = 0;
wr_rightv = wr_leftv = 0;
bg = wall_modifier(row, col); bg = wall_modifier(row, col);
if (bg == 3) { /* WWW */
wr_rndblk = (uint8_t)(prandom(4) == 0);
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)prandom(3); }
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(4); }
} else if (bg == 0) { /* SWS */
if (prandom(6) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(1); }
} else if (bg == 1) { /* SWW */
wr_rndblk = (uint8_t)(prandom(4) == 0);
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)prandom(3); }
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(3); }
} else { /* WWS (bg==2) */
if (prandom(4) == 0) { wr_right = 1; wr_rightv = (uint8_t)(prandom(1) + 2); }
if (prandom(4) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(4); }
}
rnd_seed = saved;
if (idx >= 0 && idx < WP_N) {
wp_a[idx] = (uint8_t)(wr_mid_div | (wr_mid_off << 1) |
(wr_bot_div << 4) | (wr_bot_off << 5));
wp_b[idx] = (uint8_t)(wr_rndblk | (wr_right << 1) | (wr_rightv << 2) |
(wr_left << 4) | (wr_leftv << 5));
wp_ok[idx] = 1;
}
}
/* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */
static void wall_pattern(int row, int col, int which_part)
{
int xh = COL_XH[col];
int dby = 63 * row + 65;
uint8_t bg = wall_modifier(row, col);
wall_rnd(row, col);
if (bg == 3) { /* WWW */ if (bg == 3) { /* WWW */
if (which_part) { if (which_part) {
if (prandom(4) == 0) if (wr_rndblk)
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42); wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21); wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
} }
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby); wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby);
if (which_part) { if (which_part) {
if (prandom(4) == 0) if (wr_right)
draw_right_mark(prandom(3), mid_off, row, col); draw_right_mark(wr_rightv, wr_mid_off, row, col);
if (prandom(4) == 0) if (wr_left)
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col); draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
} }
} else if (bg == 0) { /* SWS */ } else if (bg == 0) { /* SWS */
if (which_part) { if (which_part && wr_left)
if (prandom(6) == 0) draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
draw_left_mark(prandom(1), mid_off - mid_div, bot_off - bot_div, row, col);
}
} else if (bg == 1) { /* SWW */ } else if (bg == 1) { /* SWW */
if (which_part) { if (which_part) {
if (prandom(4) == 0) if (wr_rndblk)
wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42); wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42);
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21); wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
if (prandom(4) == 0) if (wr_right)
draw_right_mark(prandom(3), mid_off, row, col); draw_right_mark(wr_rightv, wr_mid_off, row, col);
if (prandom(4) == 0) if (wr_left)
draw_left_mark(prandom(3), mid_off - mid_div, bot_off - bot_div, row, col); draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
} }
} else { /* WWS (bg==2) */ } else { /* WWS (bg==2) */
if (which_part) if (which_part)
wall_b((uint8_t)(RES_WALL_DIVIDER1 + mid_div), (xh + 1) * 8 + mid_off, dby - 21); wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21);
wall_b((uint8_t)(RES_WALL_DIVIDER1 + bot_div), xh * 8 + bot_off, dby); wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby);
if (which_part) { if (which_part) {
if (prandom(4) == 0) if (wr_right)
draw_right_mark((uint16_t)(prandom(1) + 2), mid_off, row, col); draw_right_mark(wr_rightv, wr_mid_off, row, col);
if (prandom(4) == 0) if (wr_left)
draw_left_mark(prandom(4), mid_off - mid_div, bot_off - bot_div, row, col); draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col);
} }
} }
rnd_seed = saved;
} }
/* topright тайла снизу-слева (seg008.c:410). */ /* topright тайла снизу-слева (seg008.c:410). */
@@ -636,7 +747,7 @@ static void draw_tile(int row, int col)
* Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать * Порт SDLPoP redraw_tile_height: каждый кадр анимации перерисовать
* ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif * ПОЛНОСТЬЮ и loose-тайл, и ПРАВЫЙ СОСЕД (грани loose анимируются по modif
* внутри draw_tile). */ * внутри draw_tile). */
void pop_loose_shake_draw(int row, int col) void pop_loose_shake_draw(int row, int col) __banked
{ {
int x = COL_XH[col] * 8; int x = COL_XH[col] * 8;
/* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх — /* heal возвращает печёный фон обоих тайлов из ОЗУ-копии, поверх —
@@ -658,7 +769,7 @@ void pop_loose_shake_draw(int row, int col)
* читает lmod = g_bg[row*10+col] = живой modif пики). g_bg должен указывать * читает lmod = g_bg[row*10+col] = живой modif пики). g_bg должен указывать
* на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile. * на room_modif (enter_room передаёт его как bg). Порт redraw_21h/draw_tile.
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */ * heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
void pop_spike_redraw(int row, int col) void pop_spike_redraw(int row, int col) __banked
{ {
int x = COL_XH[col] * 8; int x = COL_XH[col] * 8;
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ /* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
@@ -685,7 +796,7 @@ void pop_spike_redraw(int row, int col)
* room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake): * room_modif соседа перед вызовом). Bar-в-чёрный + redraw (как loose bake):
* heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары * heal вернул бы ЗАПЕЧЁННОЕ состояние ворот → при открытии нижние бары
* оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */ * оставались бы; чёрная плита стирает старые бары в VRAM+ОЗУ-копию. */
void pop_room_redraw_seam_left(void) void pop_room_redraw_seam_left(void) __banked
{ {
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK); setfillstyle(SOLID_FILL, BLACK);
@@ -707,7 +818,7 @@ void pop_room_redraw_seam_left(void)
* после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */ * после провала (NORMAL, в ОЗУ-копию, чтобы heal её сохранял). */
#define CEIL_BAND_H 8 /* видимая высота полосы (room-space y 0..7) */ #define CEIL_BAND_H 8 /* видимая высота полосы (room-space y 0..7) */
void pop_ceil_shake_draw(int col) void pop_ceil_shake_draw(int col) __banked
{ {
int x = COL_XH[col] * 8; int x = COL_XH[col] * 8;
heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */ heal_off(x, 0, 64, CEIL_BAND_H); /* фон полосы обоих тайлов */
@@ -719,7 +830,7 @@ void pop_ceil_shake_draw(int col)
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
} }
void pop_ceil_bake_empty(int col) void pop_ceil_bake_empty(int col) __banked
{ {
int x = COL_XH[col] * 8; int x = COL_XH[col] * 8;
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
@@ -742,7 +853,7 @@ void pop_ceil_bake_empty(int col)
* draw_main_y 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька * draw_main_y 40). Зелье: маска (23, «прорезает» фон) + кадр пузырька
* potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y * potion_fram_bubb[modif & 7] на xh+3/xl=1, ybottom = draw_main_y
* (pot_size<<2) 14 (seg008:668). */ * (pot_size<<2) 14 (seg008:668). */
void pop_torch_draw(int row, int col, uint8_t modif) void pop_torch_draw(int row, int col, uint8_t modif) __banked
{ {
/* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = COL_XH[col+1] + 1 /* Пламя рисует ПРАВЫЙ сосед со своим draw_xh: xh = COL_XH[col+1] + 1
* (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env * (seg008:560), т.е. x = (col+1)*32 + 8 — как и основание факела (env
@@ -759,7 +870,7 @@ void pop_torch_draw(int row, int col, uint8_t modif)
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
} }
void pop_potion_draw(int row, int col, uint8_t modif) void pop_potion_draw(int row, int col, uint8_t modif) __banked
{ {
uint8_t type = potion_type(modif); uint8_t type = potion_type(modif);
int pot_size = (type >= 2 && type < 5) ? 1 : 0; int pot_size = (type >= 2 && type < 5) ? 1 : 0;
@@ -777,7 +888,7 @@ void pop_potion_draw(int row, int col, uint8_t modif)
* (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный + * (draw_tile подставляет пол/stuck по таймеру связи). Bar-в-чёрный +
* redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе * redraw — иначе «шишка» отжатой кнопки (base 147) осталась бы при переходе
* в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */ * в пол. Звать из pop_process_trobs для кнопок текущей комнаты. */
void pop_floor_bake(int row, int col) void pop_floor_bake(int row, int col) __banked
{ {
int x = COL_XH[col] * 8; int x = COL_XH[col] * 8;
int yb = POP_YOFF + 63 * row; int yb = POP_YOFF + 63 * row;
@@ -795,13 +906,13 @@ void pop_floor_bake(int row, int col)
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
} }
void pop_button_redraw(int row, int col) { pop_floor_bake(row, col); } void pop_button_redraw(int row, int col) __banked { pop_floor_bake(row, col); }
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка, /* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет, * см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое. * а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
* Куски двери непрозрачные — wipe не нужен. */ * Куски двери непрозрачные — wipe не нужен. */
void pop_leveldoor_redraw(int row, int col) void pop_leveldoor_redraw(int row, int col) __banked
{ {
gfx_set_bank(GFX_BANK_TRANSPARENT); gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(row, col); draw_tile(row, col);
@@ -814,7 +925,7 @@ void pop_leveldoor_redraw(int row, int col)
* рисовал loose-правый-край env_b(42); теперь пусто → чистая грань). * рисовал loose-правый-край env_b(42); теперь пусто → чистая грань).
* g_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые * g_fg[pos] уже = 0 (pop_map). Чёрная плита (NORMAL) стирает старые
* пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */ * пиксели пола в видео-ОЗУ + ОЗУ-копию, поверх — новый статич. фон. */
void pop_loose_bake_empty(int row, int col) void pop_loose_bake_empty(int row, int col) __banked
{ {
int x = COL_XH[col] * 8; int x = COL_XH[col] * 8;
int xr = COL_XH[col + 1] * 8; int xr = COL_XH[col + 1] * 8;
@@ -877,7 +988,8 @@ static mob_t mobs[MOB_MAX];
/* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на /* Кусок «приземлился» (порт loose_land): 0 = нет, иначе tilepos+1 тайла, на
* который он лёг → приложение/pop_map ставит debris + do_knock. */ * который он лёг → приложение/pop_map ставит debris + do_knock. */
uint8_t pop_loose_landed; /* pop_loose_landed переехал в pop_state.c: его читает pop_map (W1/W2), а
* писучие данные банкового модуля недоступны снаружи (см. pop_state.h). */
/* y_loose_land / y_something (seg007): [row+1], row = 1..3. Ряд 1 — /* y_loose_land / y_something (seg007): [row+1], row = 1..3. Ряд 1 —
* плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный * плита-потолок (ряд 2 комнаты сверху): в оригинале это отрицательный
@@ -886,7 +998,7 @@ static const int16_t MOB_Y_LAND[5] = {2, 65, 128, 191, 254};
static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25}; static const int16_t MOB_Y_BOUND[5] = {-1, 62, 125, 188, 25};
#define MOB_Y_NONE 0x7FFF #define MOB_Y_NONE 0x7FFF
void pop_loose_mob_spawn(int row, int col) void pop_loose_mob_spawn(int row, int col) __banked
{ {
uint8_t i; uint8_t i;
mob_t *m = 0; mob_t *m = 0;
@@ -923,7 +1035,7 @@ static int8_t obj_col, obj_row;
static void mob_render(mob_t *m, uint8_t pg); static void mob_render(mob_t *m, uint8_t pg);
void pop_loose_mob_reset(void) void pop_loose_mob_reset(void) __banked
{ {
uint8_t i; uint8_t i;
for (i = 0; i < MOB_MAX; i++) { for (i = 0; i < MOB_MAX; i++) {
@@ -988,7 +1100,7 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
mob_render(m, pg); mob_render(m, pg);
} }
void pop_loose_mob_tick(void) void pop_loose_mob_tick(void) __banked
{ {
uint8_t pg = gfx_get_draw_page() & 1, i; uint8_t pg = gfx_get_draw_page() & 1, i;
for (i = 0; i < MOB_MAX; i++) for (i = 0; i < MOB_MAX; i++)
@@ -1038,7 +1150,7 @@ static void mob_render(mob_t *m, uint8_t pg)
/* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать /* Отложенная отрисовка куска ПОВЕРХ Kid (он ниже Kid по obj_y). Звать
* после kid_draw, до fore-слоя. */ * после kid_draw, до fore-слоя. */
void pop_loose_mob_draw_over(void) void pop_loose_mob_draw_over(void) __banked
{ {
uint8_t pg = gfx_get_draw_page() & 1, i; uint8_t pg = gfx_get_draw_page() & 1, i;
for (i = 0; i < MOB_MAX; i++) for (i = 0; i < MOB_MAX; i++)
@@ -1047,7 +1159,7 @@ void pop_loose_mob_draw_over(void)
/* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и /* Позиция падающего куска для check_loose_fall_on_kid (pop_map): колонка и
* y (низ куска). 0 — куска нет. */ * y (низ куска). 0 — куска нет. */
uint8_t pop_loose_mob_pos(int *col, int *y) uint8_t pop_loose_mob_pos(int *col, int *y) __banked
{ {
uint8_t i; uint8_t i;
for (i = 0; i < MOB_MAX; i++) for (i = 0; i < MOB_MAX; i++)
@@ -1062,7 +1174,7 @@ uint8_t pop_loose_mob_pos(int *col, int *y)
* кадрах падения — гейт по флагу). */ * кадрах падения — гейт по флагу). */
static uint8_t border_dirty; static uint8_t border_dirty;
void pop_clip_sprite(int x, int w, int sy_top, int h) void pop_clip_sprite(int x, int w, int sy_top, int h) __banked
{ {
int sy_bot = sy_top + h; int sy_bot = sy_top + h;
(void)x; (void)w; (void)x; (void)w;
@@ -1074,7 +1186,7 @@ void pop_clip_sprite(int x, int w, int sy_top, int h)
* вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает * вышел за поле в последние 2 кадра) — иначе ноль стоимости. Возвращает
* чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу * чёрный из ОЗУ-копии (вне поля она чёрная) в верхний борт и статус-полосу
* + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */ * + нижний борт. Звать КАЖДЫЙ кадр ПОСЛЕ всей отрисовки спрайтов. */
void pop_room_clip_borders(void) void pop_room_clip_borders(void) __banked
{ {
if (!border_dirty) return; if (!border_dirty) return;
border_dirty--; border_dirty--;
@@ -1084,10 +1196,11 @@ void pop_room_clip_borders(void)
void pop_room_draw(uint8_t room_num, void pop_room_draw(uint8_t room_num,
const uint8_t *fg, const uint8_t *bg, const uint8_t *fg, const uint8_t *bg,
const uint8_t *lcol_fg, const uint8_t *lcol_bg) const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked
{ {
int row, col; int row, col;
g_room = room_num; g_room = room_num;
wall_pattern_reset(); /* кэш кладки привязан к номеру комнаты */
g_fg = fg; g_bg = bg; g_fg = fg; g_bg = bg;
g_lfg = lcol_fg; g_lbg = lcol_bg; g_lfg = lcol_fg; g_lbg = lcol_bg;
@@ -1125,10 +1238,7 @@ static int8_t col_from_x(int xpos) /* get_tile_div_mod: колонка (
return (int8_t)xh; return (int8_t)xh;
} }
static int8_t y_to_row(int y) /* y_to_row_mod4 (seg006, TILE_SIZEY=63) */ #define y_to_row(y) pop_y_to_row((int16_t)(y)) /* общий (pop_geom) */
{
return (int8_t)((y + 60) / 63 % 4 - 1);
}
static void fore_only_tile(int row, int col) static void fore_only_tile(int row, int col)
{ {
@@ -1180,8 +1290,25 @@ static void ceil_over_kid_tile(int col)
fore_only_tile(-1, col); /* draw_tile_fore */ fore_only_tile(-1, col); /* draw_tile_fore */
} }
/* Пересекается ли габарит тайла с окном fore-слоя. Шаг тайла — 32 px
* (COL_XH[col]*8 = col*32), куски выходят вправо максимум на +8 (дивайдеры
* и метки рисуются в ячейке правого соседа) и вверх на 42+высота кадра.
* Даёт отсев ЦЕЛОГО тайла ДО обращения к атласу: спрайт 27..40 px шириной
* задевает не все тайлы футпринта, а платили за каждый (atlas_image +
* gfx_w0_map + разбор заголовка на КАЖДЫЙ кусок). */
static uint8_t tile_in_fclip(int row, int col)
{
int x0, y0;
if (!fclip_on) return 1;
x0 = COL_XH[col] * 8;
y0 = POP_YOFF + 63 * row;
return (uint8_t)(x0 < fclip_x1 && x0 + 40 > fclip_x0 &&
y0 - 8 < fclip_y1 && y0 + 70 > fclip_y0);
}
static void fore_tile(int row, int col) /* передний слой одного тайла */ static void fore_tile(int row, int col) /* передний слой одного тайла */
{ {
if (!tile_in_fclip(row, col)) return;
/* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал /* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал
* кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в * кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в
* backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ * backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ
@@ -1214,7 +1341,9 @@ static void climb_overlay_tile(int row, int col, uint8_t fidx)
{ {
/* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую /* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую
* Kid спускается/подтягивается, закрашивается целым тайлом */ * Kid спускается/подтягивается, закрашивается целым тайлом */
uint8_t code = tile_code_drawn(row, col); uint8_t code;
if (!tile_in_fclip(row, col)) return;
code = tile_code_drawn(row, col);
int x, dmy, dby; int x, dmy, dby;
const piece *t; const piece *t;
if (tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */ if (tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */
@@ -1304,8 +1433,9 @@ static void overlay_mid_tile(int row, int col)
* сразу), поэтому порядок эмулируется явной проверкой. */ * сразу), поэтому порядок эмулируется явной проверкой. */
static void other_overlay_tile(int row, int col) static void other_overlay_tile(int row, int col)
{ {
uint8_t code = tile_code(row, col); uint8_t code, left;
uint8_t left; if (!tile_in_fclip(row, col)) return;
code = tile_code(row, col);
/* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для /* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для
* него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань * него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань
* пустого соседа). В оригинале она уходит в y-сортируемый midtable и * пустого соседа). В оригинале она уходит в y-сортируемый midtable и
@@ -1328,13 +1458,20 @@ static void other_overlay_tile(int row, int col)
} }
} }
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, /* Футпринт персонажа в тайлах (порт set_char_collision, seg006:0723).
int8_t dir, uint8_t frame, uint8_t action) * Выделено из pop_fore_over_kid, потому что тем же счётом пользуется страж
* (pop_fore_over_char): в оригинале это общий код для любого Char.
* Результат — в файловых статиках, а не через 6 указателей: на Z80 такой
* возврат стоил бы дороже самой функции. */
static int8_t fp_cL, fp_cR, fp_cLraw, fp_rT, fp_rB, fp_rTraw;
static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir)
{ {
int wh = (int)((w + 1) >> 1); /* char_width_half */ int wh = (int)((w + 1) >> 1); /* char_width_half */
int xl = obj_x / 2 + 58; /* char_x_left (58-база) */ int xl = obj_x / 2 + 58; /* char_x_left (58-база) */
int xr; int xr;
int8_t cL, cR, cLraw, rT, rB, r, c, rTraw; int8_t cL, cR, cLraw, rT, rB, rTraw;
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */ if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
xr = xl + wh; xr = xl + wh;
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */ cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
@@ -1362,6 +1499,18 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
* (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим * (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим
* включение ряда rB-1. */ * включение ряда rB-1. */
if (rB > 0 && rT >= rB) rT = rB - 1; if (rB > 0 && rT >= rB) rT = rB - 1;
fp_cL = cL; fp_cR = cR; fp_cLraw = cLraw;
fp_rT = rT; fp_rB = rB; fp_rTraw = rTraw;
}
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir, uint8_t frame, uint8_t action) __banked
{
int8_t cL, cR, cLraw, rT, rB, rTraw, r, c;
fclip_on = 1; /* окно = прямоугольник спрайта */
char_footprint(obj_x, obj_y, w, h, dir);
cL = fp_cL; cR = fp_cR; cLraw = fp_cLraw;
rT = fp_rT; rB = fp_rB; rTraw = fp_rTraw;
/* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт /* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт
* Kid попадает в midtable — база порядка окклюзии в other_overlay_tile. * Kid попадает в midtable — база порядка окклюзии в other_overlay_tile.
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/ * run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
@@ -1429,4 +1578,37 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
g_clip_top = 0; g_clip_top = 0;
} }
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
fclip_on = 0;
}
/* Передние грани тайлов поверх ПРОИЗВОЛЬНОГО персонажа (страж, дальше —
* тень/скелет). В оригинале любой Char — запись midtable, а foretable
* рисуется после всех midtable (draw_tile_fore, seg008:690), поэтому
* колонна и кромка пола перекрывают всех одинаково.
*
* Отличие от pop_fore_over_kid: только слой fore (+ полоса потолка).
* Оверлеи тела пола (draw_other_overlay) завязаны на позы Kid — вис,
* полёт, подъём, — которых у стража нет: он ходит и дерётся на полу
* (seq_77..seq_84). Появятся падающие/висящие стражи — тогда и общий
* порт redraw_at_char2, а не догадки сейчас. */
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir)
{
int8_t r, c;
fclip_on = 1; /* окно = прямоугольник спрайта */
char_footprint(obj_x, obj_y, w, h, dir);
gfx_set_bank(GFX_BANK_SPRITE);
for (r = fp_rT; r <= fp_rB; r++)
for (c = fp_cL; c <= fp_cR; c++)
fore_tile(r, c);
if (fp_rTraw < 0) {
g_clip_top = POP_YOFF;
for (c = fp_cL; c <= fp_cR; c++) {
ov_mark(COL_XH[c] * 8, 0, 64, CEIL_BAND_H);
ceil_over_kid_tile(c);
}
g_clip_top = 0;
}
gfx_set_bank(GFX_BANK_NORMAL);
fclip_on = 0;
} }
+42 -28
View File
@@ -28,10 +28,10 @@
/* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load /* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load
* трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ * трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ
* после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */ * после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */
int pop_bg_load(void); int pop_bg_load(void) __banked;
/* Освободить EMM-страницы атласов. */ /* Освободить EMM-страницы атласов. */
void pop_bg_free(void); void pop_bg_free(void) __banked;
/* Нарисовать комнату: fg[30]/bg[30] — foretable-коды/backtable-модификаторы /* Нарисовать комнату: fg[30]/bg[30] — foretable-коды/backtable-модификаторы
* (по строкам, row*10+col). lcol_fg/lcol_bg — 3 тайла правого столбца * (по строкам, row*10+col). lcol_fg/lcol_bg — 3 тайла правого столбца
@@ -39,97 +39,111 @@ void pop_bg_free(void);
* тогда левая кромка = стена. room_num — номер комнаты (зерно стен). */ * тогда левая кромка = стена. room_num — номер комнаты (зерно стен). */
void pop_room_draw(uint8_t room_num, void pop_room_draw(uint8_t room_num,
const uint8_t *fg, const uint8_t *bg, const uint8_t *fg, const uint8_t *bg,
const uint8_t *lcol_fg, const uint8_t *lcol_bg); const uint8_t *lcol_fg, const uint8_t *lcol_bg) __banked;
/* Задать ряд 0 комнаты СНИЗУ (fg-коды, 10 шт.) — для draw_tile_topright, /* Задать ряд 0 комнаты СНИЗУ (fg-коды, 10 шт.) — для draw_tile_topright,
* когда тайл нижнего ряда стал empty (после падения loose): «стена вниз» * когда тайл нижнего ряда стал empty (после падения loose): «стена вниз»
* рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */ * рисуется по коду room_B(0, col-1). 0 — нет комнаты снизу. */
void pop_room_set_below(const uint8_t *below_row0_fg); void pop_room_set_below(const uint8_t *below_row0_fg) __banked;
/* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы /* Задать ряд 2 (fg-коды + модификаторы, 10 шт.) комнаты СВЕРХУ — для полосы
* кладки у потолка (seg008 draw_room доп. верхний ряд из room_A). 0 — нет * кладки у потолка (seg008 draw_room доп. верхний ряд из room_A). 0 — нет
* комнаты сверху (кромка уровня → пол). */ * комнаты сверху (кромка уровня → пол). */
void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod); void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_mod) __banked;
/* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него — /* Перерисовать передний слой (fore) тайлов футпринта Kid ПОВЕРХ него —
* звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ * звать ПОСЛЕ kid_draw. obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ
* спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики * спрайта, w/h — размер кадра, dir — направление Kid (<0 влево). Метрики
* берутся из pop_kid (kid_fp_*). См. redraw_at_char/set_char_collision. */ * берутся из pop_kid (kid_fp_*). См. redraw_at_char/set_char_collision. */
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir, uint8_t frame, uint8_t action); int8_t dir, uint8_t frame, uint8_t action) __banked;
/* Окно fore-слоя: прямоугольник СПРАЙТА (экранные координаты, y уже с
* POP_YOFF), поверх которого fore-проход возвращает куски тайлов. Ставит
* тот, кто нарисовал спрайт (kid_draw / pop_guard_draw) — он один знает
* итоговый габарит (у Kid — с учётом клинка и обрезки clip_char). Вне
* fore-проходов окно игнорируется: фон рисуется целиком. Мотивация и
* замер — в pop_bg.c у fclip_on. */
void pop_fore_set_clip(int x, int y, int w, int h) __banked;
/* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка,
* без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата
* (до ×8/7), как у kid_fp_obj_x(). */
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir);
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ /* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за * (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
* прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице * прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице
* дабл-буфера остаётся след и мерцает. Звать рядом с kid_heal. */ * дабл-буфера остаётся след и мерцает. Звать рядом с kid_heal. */
void pop_fore_heal(void); void pop_fore_heal(void) __banked;
/* Loose floors — динамическая отрисовка (состояние pop_loose_modif в /* Loose floors — динамическая отрисовка (состояние pop_loose_modif в
* pop_map). Звать на ТЕКУЩЕЙ (back) странице из pop_loose_tick. * pop_map). Звать на ТЕКУЩЕЙ (back) странице из pop_loose_tick.
* shake_draw — дрожащий кадр (heal+SPRITE); bake_empty — запечь пустоту * shake_draw — дрожащий кадр (heal+SPRITE); bake_empty — запечь пустоту
* после падения (NORMAL, в ОЗУ-копию). */ * после падения (NORMAL, в ОЗУ-копию). */
void pop_loose_shake_draw(int row, int col); void pop_loose_shake_draw(int row, int col) __banked;
void pop_loose_bake_empty(int row, int col); void pop_loose_bake_empty(int row, int col) __banked;
/* Плита-ПОТОЛОК (loose ряда 2 комнаты СВЕРХУ, наш ряд −1): дрожащий кадр в /* Плита-ПОТОЛОК (loose ряда 2 комнаты СВЕРХУ, наш ряд −1): дрожащий кадр в
* полосе у потолка / «запечь» пустоту (колодец наверх) после провала. * полосе у потолка / «запечь» пустоту (колодец наверх) после провала.
* Состояние — pop_ceil_modif[10] в pop_map. */ * Состояние — pop_ceil_modif[10] в pop_map. */
void pop_ceil_shake_draw(int col); void pop_ceil_shake_draw(int col) __banked;
void pop_ceil_bake_empty(int col); void pop_ceil_bake_empty(int col) __banked;
/* Кусок приземлился (loose_land): 0 = нет, иначе tilepos+1 тайла, на который /* Кусок приземлился (loose_land) — переехал в pop_state.h: переменную
* он лёг. Ставит pop_loose_mob_tick, разбирает pop_loose_tick (debris+knock). */ * читают по обе стороны банковой границы. */
extern uint8_t pop_loose_landed; #include "pop_state.h"
/* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому /* Пики (spike) — перерисовка тайла (row,col) на back-странице по живому
* modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */ * modif (pop_trob). Звать из pop_process_trobs. См. pop_bg.c. */
void pop_spike_redraw(int row, int col); void pop_spike_redraw(int row, int col) __banked;
/* Анимация факела/зелья (динамика каждый кадр; фон запечён без них): /* Анимация факела/зелья (динамика каждый кадр; фон запечён без них):
* modif — живой room_modif тайла. Для факела col — колонка САМОГО факела * modif — живой room_modif тайла. Для факела col — колонка САМОГО факела
* (пламя рисуется в ячейке правого соседа). */ * (пламя рисуется в ячейке правого соседа). */
void pop_torch_draw(int row, int col, uint8_t modif); void pop_torch_draw(int row, int col, uint8_t modif) __banked;
void pop_potion_draw(int row, int col, uint8_t modif); void pop_potion_draw(int row, int col, uint8_t modif) __banked;
/* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева. /* Ворота на ЛЕВОМ шве (col0) — перерисовать по живому modif соседа слева.
* g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла * g_lbg (lcol_bg) должен быть обновлён вызывающим. Звать из основного цикла
* на back-странице, когда сосед слева анимируется. См. pop_bg.c. */ * на back-странице, когда сосед слева анимируется. См. pop_bg.c. */
void pop_room_redraw_seam_left(void); void pop_room_redraw_seam_left(void) __banked;
/* Перерисовать ПОЛ тайла (row,col) + правую грань в соседе на back-странице /* Перерисовать ПОЛ тайла (row,col) + правую грань в соседе на back-странице
* (wipe по площади пола, БЕЗ стены ряда ниже). Для смены тайла на месте: * (wipe по площади пола, БЕЗ стены ряда ниже). Для смены тайла на месте:
* кнопка нажата/отжата, floor → debris от упавшей плиты. */ * кнопка нажата/отжата, floor → debris от упавшей плиты. */
void pop_floor_bake(int row, int col); void pop_floor_bake(int row, int col) __banked;
void pop_button_redraw(int row, int col); /* = pop_floor_bake (кнопка) */ void pop_button_redraw(int row, int col) __banked; /* = pop_floor_bake (кнопка) */
/* Дверь уровня: перерисовать её ПРАВУЮ половину (row, col = правая) с /* Дверь уровня: перерисовать её ПРАВУЮ половину (row, col = правая) с
* запечкой — створка анимируется (pop_trob animate_leveldoor). */ * запечкой — створка анимируется (pop_trob animate_leveldoor). */
void pop_leveldoor_redraw(int row, int col); void pop_leveldoor_redraw(int row, int col) __banked;
/* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла /* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick * (row,col) и покадровое продвижение+отрисовка на back-странице. tick
* зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */ * зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */
void pop_loose_mob_spawn(int row, int col); void pop_loose_mob_spawn(int row, int col) __banked;
void pop_loose_mob_tick(void); void pop_loose_mob_tick(void) __banked;
void pop_loose_mob_reset(void); /* остановить кусок (смена комнаты) */ void pop_loose_mob_reset(void) __banked; /* остановить кусок (смена комнаты) */
/* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs, /* Кусок НИЖЕ Kid по obj_y → рисуется ПОВЕРХ него (порт sort_curr_objs,
* seg008:1561). Звать после kid_draw, перед fore-слоем. */ * seg008:1561). Звать после kid_draw, перед fore-слоем. */
void pop_loose_mob_draw_over(void); void pop_loose_mob_draw_over(void) __banked;
/* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */ /* Позиция куска (колонка, y низа) для check_loose_fall_on_kid. 0 — нет. */
uint8_t pop_loose_mob_pos(int *col, int *y); uint8_t pop_loose_mob_pos(int *col, int *y) __banked;
/* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом /* Детектор выхода спрайта за поле: если спрайт [x,w] с ЭКРАННЫМ верхом
* sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше * sy_top и высотой h заходит ниже поля (статус-полоса/борт) или выше
* верхнего борта — взводит внутренний флаг «борта грязные» на 2 кадра. * верхнего борта — взводит внутренний флаг «борта грязные» на 2 кадра.
* Звать СРАЗУ после блита падающего объекта (Kid/кусок). */ * Звать СРАЗУ после блита падающего объекта (Kid/кусок). */
void pop_clip_sprite(int x, int w, int sy_top, int h); void pop_clip_sprite(int x, int w, int sy_top, int h) __banked;
/* Почистить борта (верхний + статус-полоса/нижний) — но ТОЛЬКО в кадрах, /* Почистить борта (верхний + статус-полоса/нижний) — но ТОЛЬКО в кадрах,
* где кто-то реально вышел за поле (гейт по флагу от pop_clip_sprite): * где кто-то реально вышел за поле (гейт по флагу от pop_clip_sprite):
* даром в покое, O(1) при падении (не зависит от числа спрайтов). Звать * даром в покое, O(1) при падении (не зависит от числа спрайтов). Звать
* КАЖДЫЙ кадр ПОСЛЕ kid_draw+fore на back-странице. */ * КАЖДЫЙ кадр ПОСЛЕ kid_draw+fore на back-странице. */
void pop_room_clip_borders(void); void pop_room_clip_borders(void) __banked;
#endif #endif
+32
View File
@@ -0,0 +1,32 @@
/*
* pop_char.h — состояние ПЕРСОНАЖА, общее для Кида, стража и активной
* копии Char. Порт char_type (types.h:302).
*
* Зачем отдельным заголовком: интерпретатор последовательностей в
* оригинале один на всех (работает с Char, а вокруг стоят
* loadkid/savekid и loadshad/saveshad, seg006:809). Пока у Кида был свой
* усечённый тип, а у стража свой, общий интерпретатор был невозможен.
* Поля и их порядок — как в char_type, чтобы порт seg005/seg006 ложился
* один в один.
*/
#ifndef POP_CHAR_H
#define POP_CHAR_H
#include <stdint.h>
typedef struct {
uint8_t frame;
uint8_t x, y;
int8_t direction; /* -1 влево, 0 вправо */
int8_t curr_col, curr_row;
uint8_t action;
int8_t fall_x, fall_y;
uint8_t room;
uint8_t repeat;
uint8_t charid; /* 0 = Kid, 2 = страж (см. CHARID_* в pop_guard.h) */
uint8_t sword;
int8_t alive; /* -1 = жив (как в оригинале), 0 = убит */
uint16_t curr_seq; /* адрес в seqtbl (база SEQTBL_BASE) */
} pop_char_t;
#endif
+21
View File
@@ -0,0 +1,21 @@
/*
* pop_cheat.h — режим читов (порт cheats_enabled, seg000:111).
*
* В оригинале включается ключом командной строки megahit/debug и меняет
* набор доступных клавиш. У нас конфигурации/UI пока нет, поэтому это
* просто глобальный флаг: main ставит его при старте (на время разработки —
* ВКЛЮЧЁН). Клавиши читов обрабатывает главный цикл.
*
* Реализовано: K — убить стража (seg000:786). Остальные читы оригинала
* (время, перо, воскрешение, переход по комнатам) пока не портированы.
*/
#ifndef POP_CHEAT_H
#define POP_CHEAT_H
#include <stdint.h>
extern uint8_t pop_cheats; /* 0 = выключены, иначе включены */
#define KBD_CHEAT_KILL 0x42 /* K (PS/2 set 2) — kill guard */
#endif
+28 -15
View File
@@ -75,13 +75,20 @@
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между * автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
* кадрами (rest/save). */ * кадрами (rest/save). */
static int8_t control_x, control_y, control_shift; static int8_t control_x, control_y, control_shift;
static int8_t control_forward, control_backward, control_up, control_down, control_shift2; /* НЕ static: в оригинале это глобалы, через которые ИИ стража подаёт
* СИНТЕТИЧЕСКИЙ ввод (autocontrol_*, seg002), а дальше работает тот же
* control(), что и у игрока. Поэтому шина общая. */
int8_t control_forward, control_backward, control_up, control_down, control_shift2;
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2; static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ * /* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
* Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl * Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl
* не дублируются в этот TU). */ * не дублируются в этот TU). */
#define seqtbl_offset_char(seq) kid_set_seq(seq) /* В оригинале seqtbl_offset_char работает с АКТИВНЫМ персонажем, и
* control() вызывается внутри окна Char. Раньше макрос вёл на
* kid_set_seq (пишет прямо в Kid) — после введения окна это ломало ввод:
* savekid затирал Kid копией Char со старой curr_seq. */
#define seqtbl_offset_char(seq) pop_char_set_seq(seq)
static int8_t release_arrows(void) static int8_t release_arrows(void)
{ {
@@ -125,7 +132,7 @@ static void run_jump(void)
{ {
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это /* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
* полировка K3; K2b просто запускает run-jump. */ * полировка K3; K2b просто запускает run-jump. */
if (Kid.frame >= FRAME_7_RUN) { if (Char.frame >= FRAME_7_RUN) {
control_up = release_arrows(); control_up = release_arrows();
seqtbl_offset_char(SEQ_4_RUN_JUMP); seqtbl_offset_char(SEQ_4_RUN_JUMP);
} }
@@ -190,15 +197,15 @@ static void safe_step(void)
control_forward = CONTROL_IGNORE; control_forward = CONTROL_IGNORE;
distance = pop_edge_distance(); distance = pop_edge_distance();
if (distance) { if (distance) {
Kid.repeat = 1; Char.repeat = 1;
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE)); seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
} else if (pop_edge_type() == EDGE_WALL) { } else if (pop_edge_type() == EDGE_WALL) {
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад /* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */ * (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
Kid.repeat = 1; Char.repeat = 1;
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE)); seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
} else if (Kid.repeat != 0) { } else if (Char.repeat != 0) {
Kid.repeat = 0; Char.repeat = 0;
seqtbl_offset_char(SEQ_44_STEP_ON_EDGE); seqtbl_offset_char(SEQ_44_STEP_ON_EDGE);
} else { } else {
seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */ seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */
@@ -266,7 +273,7 @@ static void control_running(void)
/* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок /* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок
* seq_47 при беге в стену (как оригинал), с корректной позицией. */ * seq_47 при беге в стену (как оригинал), с корректной позицией. */
if (control_x == CONTROL_RELEASED && if (control_x == CONTROL_RELEASED &&
(Kid.frame == FRAME_7_RUN || Kid.frame == FRAME_11_RUN)) { (Char.frame == FRAME_7_RUN || Char.frame == FRAME_11_RUN)) {
control_forward = release_arrows(); control_forward = release_arrows();
seqtbl_offset_char(SEQ_13_STOP_RUN); seqtbl_offset_char(SEQ_13_STOP_RUN);
} else if (control_x == CONTROL_HELD_BACKWARD) { } else if (control_x == CONTROL_HELD_BACKWARD) {
@@ -318,16 +325,16 @@ static void control_jumpup(void)
} }
/* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */ /* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */
static void control(void) void pop_control(void)
{ {
uint8_t f = Kid.frame; uint8_t f = Char.frame;
/* seg005 control(): в отскоке (bumped) и в свободном падении управление /* seg005 control(): в отскоке (bumped) и в свободном падении управление
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок * НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и * прерывает присед после удара (medland/плита по голове) нажатием ↑, и
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие * последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
* стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на * стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
* пару пикселей меньше, чем должен»). */ * пару пикселей меньше, чем должен»). */
if (Kid.action == 5 || Kid.action == 4) { /* bumped / in_freefall */ if (Char.action == 5 || Char.action == 4) { /* bumped / in_freefall */
release_arrows(); release_arrows();
return; return;
} }
@@ -406,12 +413,12 @@ static void flip_control_x(void)
/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */ /* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */
static void user_control(void) static void user_control(void)
{ {
if (Kid.direction >= DIR_RIGHT) { if (Char.direction >= DIR_RIGHT) {
flip_control_x(); flip_control_x();
control(); pop_control();
flip_control_x(); flip_control_x();
} else { } else {
control(); pop_control();
} }
} }
@@ -451,6 +458,12 @@ void pop_ctrl_tick(void)
rest_ctrl_1(); rest_ctrl_1();
read_input(); read_input();
read_user_control(); read_user_control();
user_control(); /* -> control(): может сменить Kid.curr_seq */ /* Окно Char вокруг диспетчера: control() работает с АКТИВНЫМ
* персонажем (как в оригинале), поэтому ввод игрока применяется к
* Киду только внутри loadkid/savekid. Кадр не перезагружаем —
* play_seq в kid_tick сделает это следующим шагом. */
pop_loadkid();
user_control(); /* -> pop_control(): может сменить Char.curr_seq */
pop_savekid_state();
save_ctrl_1(); save_ctrl_1();
} }
+15
View File
@@ -32,4 +32,19 @@ void pop_ctrl_tick(void);
* зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */ * зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */
int8_t pop_ctrl_shift_held(void); int8_t pop_ctrl_shift_held(void);
/* ---- Шина синтетического ввода (порт глобалов control_* из seg005) ----
* В оригинале ИИ соперника (autocontrol_*, seg002) не имеет своего
* диспетчера: он ВЫСТАВЛЯЕТ эти переменные, а дальше исполняется тот же
* control(), что и для игрока. Значения — CONTROL_RELEASED/IGNORE/HELD. */
#define POP_CONTROL_RELEASED 0
#define POP_CONTROL_IGNORE 1
#define POP_CONTROL_HELD (-1)
extern int8_t control_forward, control_backward, control_up, control_down,
control_shift2;
/* Диспетчер (seg005:252) для АКТИВНОГО персонажа Char. Игрок идёт через
* pop_ctrl_tick (тот делает user_control с зеркалом «вперёд»), страж — из
* своего тика напрямую. */
void pop_control(void);
#endif #endif
+106
View File
@@ -0,0 +1,106 @@
/*
* pop_gdraw.c — ОТРИСОВКА стража. Резидент окна W3 (--w3 pop_gdraw.c).
*
* Почему отдельным файлом, а не внутри pop_guard.c: --w3 забирает МОДУЛЬ
* целиком (код+rodata в 0xC000), а состояние стража и его логика обязаны
* остаться в W1/W2 — из банка (guards.c) резидент W3 недостижим ни прямо,
* ни транзитивно (docs/layout_plan_v2.md §2 R2). Значит граница проходит
* ровно между «что рисует» (зовёт только главный цикл → резидент) и «что
* знает» (зовёт и банк → W1/W2).
*
* Писучие статики резидента (--dataseg не трогаем) остаются в обычном
* _DATA, то есть в W2 — сюда переезжает КОД, не данные.
*/
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_guard.h"
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */
#include "kid_data.h"
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */
#define GUARD_PAGES 5
static atlas_t gp[GUARD_PAGES];
static uint8_t g_ok;
/* Прямоугольник последней отрисовки ПО СТРАНИЦЕ дабл-буфера (как у Kid:
* heal обязан стирать спрайт именно той страницы, в которую рисуем). */
static int gx_l[2], gy_l[2];
static uint16_t gw_l[2], gh_l[2];
static uint8_t gvalid[2];
int pop_guard_load(void) __banked
{
static const char *const names[GUARD_PAGES] = {
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
"GUARD\\g3.atl", "GUARD\\g4.atl"
};
uint8_t i;
for (i = 0; i < GUARD_PAGES; i++)
if (atlas_load(&gp[i], names[i]) != 0) return -1;
g_ok = 1;
return 0;
}
void pop_guard_free(void) __banked
{
uint8_t i;
if (!g_ok) return;
for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]);
g_ok = 0;
}
void pop_guard_heal(void) __banked
{
uint8_t p = gfx_get_draw_page() & 1;
if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
gvalid[p] = 0;
}
void pop_guard_draw(void) __banked
{
int obj_x, obj_y, top, fwd, fp_x, bx0 = 0, top0 = 0;
uint8_t page, idx, p, flip;
const uint8_t *img;
uint16_t w, h;
if (!g_ok || Guard.charid == 0 || pop_gframe.image == 255) return;
/* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */
fwd = (int)Guard.x + ((Guard.direction < 0) ? -pop_gframe.dx : pop_gframe.dx);
obj_x = (fwd << 1) - 116;
obj_y = (int)Guard.y + pop_gframe.dy;
if ((int8_t)(pop_gframe.flags ^ Guard.direction) >= 0) obj_x++;
fp_x = obj_x; /* ЛОГИЧЕСКАЯ X (до ×8/7) — футпринт */
obj_x = obj_x * 8 / 7; /* calc_screen_x_coord */
page = (uint8_t)(pop_gframe.image >> 3);
idx = (uint8_t)(pop_gframe.image & 7);
if (page >= GUARD_PAGES) return;
img = (const uint8_t *)atlas_image(&gp[page], idx);
gfx_w0_map(gp[page].page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
if (w && h) {
int bx = obj_x;
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
if (flip) bx -= (int)w;
top = obj_y - (int)h + 1;
bx0 = bx; top0 = top;
p = gfx_get_draw_page() & 1;
gfx_set_bank(GFX_BANK_SPRITE);
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, 0, 0);
gfx_set_bank(GFX_BANK_NORMAL);
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = h; gvalid[p] = 1;
}
gfx_w0_unmap();
/* Окно fore-слоя = прямоугольник стража (см. pop_bg.c, fclip_on):
* без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов на кадр. */
pop_fore_set_clip(bx0, top0 + POP_YOFF, (int)w, (int)h);
/* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись
* midtable, а foretable рисуется после всех midtable-записей, поэтому
* колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid
* (seg008:690 draw_tile_fore). Без этого страж виден сквозь передний
* столб. */
if (w && h)
pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction);
}
+23
View File
@@ -0,0 +1,23 @@
/*
* pop_geom.c — общая геометрия + PRNG оригинала. См. pop_geom.h.
*/
#include "pop_geom.h"
/* data.h: шаг 14 (ширина тайла в координатах персонажа), [5] = col 0. */
const int16_t pop_x_bump[20] = {
-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
};
const int16_t pop_y_land[5] = {-8, 55, 118, 181, 244};
int8_t pop_y_to_row(int16_t y)
{
return (int8_t)((y + 60) / TILE_SIZEY % 4 - 1);
}
uint16_t pop_prandom(unsigned long *seed, uint16_t maxv)
{
unsigned long s = *seed * 214013UL + 2531011UL;
*seed = s;
return (uint16_t)((uint16_t)(s >> 16) % (uint16_t)(maxv + 1));
}
+34
View File
@@ -0,0 +1,34 @@
/*
* pop_geom.h — общая геометрия PoP и PRNG оригинала.
*
* Сюда сведено то, что раньше дублировалось по модулям (x_bump/y_land в
* pop_kid и pop_map, y_to_row в pop_bg и pop_map, 32-битный LCG в pop_bg и
* pop_trob). Кроме экономии это ФУНДАМЕНТ bank-safe API: модуль живёт в
* W1/W2 и не трогает графику, поэтому его может звать и главный цикл, и
* резидент W3, и будущий __banked-код стражей (docs/layout_plan_v2.md §4).
*/
#ifndef POP_GEOM_H
#define POP_GEOM_H
#include <stdint.h>
#define FIRST_ONSCREEN_COLUMN 5 /* индекс col 0 комнаты в x_bump */
#define TILE_SIZEX 14 /* ширина тайла в координатах персонажа */
#define TILE_SIZEY 63 /* высота ряда */
/* x_bump (data.h): левая граница колонки в координатах персонажа; колонка
* комнаты col = x_bump[col + FIRST_ONSCREEN_COLUMN]. y_land: пол ряда
* (индекс row+1; [0] — ряд «над комнатой»). */
extern const int16_t pop_x_bump[20];
extern const int16_t pop_y_land[5];
/* y_to_row_mod4 (seg006): ряд по координате y с оборотом mod 4 (−1 = полоса
* над комнатой, 3 = под комнатой). */
int8_t pop_y_to_row(int16_t y);
/* prandom (seg009:321) — 32-битный LCG оригинала. Сид ВНЕШНИЙ: у стен
* (раскладка кладки) и у анимаций тайлов (фаза факелов) свои
* последовательности, смешивать их нельзя. */
uint16_t pop_prandom(unsigned long *seed, uint16_t maxv);
#endif
+92
View File
@@ -0,0 +1,92 @@
/*
* pop_guard.c — СОСТОЯНИЕ и ЛОГИКА стража (W1/W2). См. pop_guard.h.
*
* Здесь только то, что должно оставаться достижимым из банка (guards.c):
* поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы.
* ОТРИСОВКА — в pop_gdraw.c, резиденте W3 (её зовёт лишь главный цикл).
*/
#include <stdint.h>
#include "pop_guard.h"
#include "pop_geom.h"
#include "pop_level.h"
#include "pop_kid.h" /* Char/play_seq — интерпретатор общий */
#include "kid_data.h"
pop_char_t Guard;
uint8_t guardhp_curr, guardhp_max;
int8_t guardhp_delta;
/* Кадр стража (порт cur_frame для Char). Глобальный, а не статик: его
* заполняет логика (W1/W2), а читает отрисовка из резидента W3. */
kframe pop_gframe;
uint8_t pop_guard_present(void)
{
return (uint8_t)(Guard.charid != 0 && Guard.alive < 0 && guardhp_curr != 0);
}
void pop_guard_reset(void)
{
Guard.charid = 0;
Guard.alive = 0;
Guard.direction = 0;
guardhp_curr = 0;
guardhp_delta = 0;
}
/* kill guard cheat (seg000:786):
* if (Guard.charid != charid_4_skeleton) {
* guardhp_delta = -guardhp_curr;
* Guard.alive = 0;
* }
* Скелета чит не убивает — он и в оригинале бессмертен. */
void pop_guard_kill(void)
{
if (Guard.charid == CHARID_4_SKELETON) return;
if (!pop_guard_present()) return;
guardhp_delta = (int8_t)-guardhp_curr;
guardhp_curr = 0;
Guard.alive = 0;
}
/* load_frame для стража (seg006:0293 ветка charid_2_guard): таблица у
* стражей СВОЯ (frame_tbl_guard), индекс = frame + add_frame 149, где
* add_frame = 70 для кадров 102..106 (seg006:535). */
void pop_guard_load_frame(void)
{
int16_t idx = (int16_t)Guard.frame;
if (idx >= 102 && idx < 107) idx += 70; /* add_frame */
idx -= 149;
if (idx < 0 || idx >= KID_NGFRAMES) { pop_gframe.image = 255; return; }
pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&pop_gframe);
}
void pop_guard_enter(uint8_t room)
{
uint8_t tile, color, skill;
int8_t dir;
pop_guard_reset();
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
* колонки тайла, а не берётся из файла. */
Guard.room = room;
Guard.curr_row = (int8_t)(tile / 10);
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
Guard.curr_col = (int8_t)(tile % 10);
Guard.direction = dir;
Guard.charid = CHARID_2_GUARD;
Guard.sword = 0; /* sword_0_sheathed */
Guard.alive = -1;
Guard.action = 1; /* actions_1_run_jump */
Guard.fall_x = Guard.fall_y = 0;
guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */
guardhp_delta = 0;
/* enter_guard (seg002:0208): seqtbl_offset_char(seq_77_guard_stand_inactive).
* Последовательность крутит общий интерпретатор (pop_guard_play), а
* таблицу кадров он выбирает по Char.charid — у стража своя. */
pop_loadshad();
pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
pop_saveshad();
pop_guard_play(); /* до первого кадра */
}
+73
View File
@@ -0,0 +1,73 @@
/*
* pop_guard.h — СОСТОЯНИЕ стража и точки входа в его логику.
*
* Разделение по окнам (docs/layout_plan_v2.md §4, подтверждено пробником
* tests/w3bankgfx):
* - СОСТОЯНИЕ живёт здесь, в W1/W2. Писучие статики __banked-модуля
* линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк —
* только код;
* - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c;
* - ОТРИСОВКА стража — в pop_gdraw.c, РЕЗИДЕНТЕ W3 (--w3): её зовёт
* только главный цикл, а из банка резидент недостижим. Так код
* отрисовки не занимает дефицитные W1/W2 (куча).
*
* Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006
* ложился один в один.
*/
#ifndef POP_GUARD_H
#define POP_GUARD_H
#include <stdint.h>
#include "kid_data.h" /* kframe */
#include "pop_char.h"
/* seqids (types.h:1070) — последовательности стража. */
#define SEQ_77_GUARD_STAND_INACTIVE 77
/* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */
#define CHARID_0_KID 0
#define CHARID_1_SHADOW 1
#define CHARID_2_GUARD 2
#define CHARID_4_SKELETON 4
#define CHARID_5_PRINCESS 5
/* Тип персонажа — в pop_char.h (общий с Кидом и Char). */
extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */
extern uint8_t guardhp_curr, guardhp_max;
extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */
/* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2,
* читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */
extern kframe pop_gframe;
void pop_guard_load_frame(void);
/* Есть ли живой страж в отрисованной комнате (для главного цикла). */
uint8_t pop_guard_present(void);
/* Атласы стража (GUARD\\g0..g4.atl) — грузить ДО initgraph, как Kid.
* 0 — OK, -1 — ошибка (тогда страж просто не рисуется). */
int pop_guard_load(void) __banked;
void pop_guard_free(void) __banked;
/* Вход в комнату: поднять стража из данных уровня (порт enter_guard,
* seg002:0112) либо убрать его, если в комнате стража нет. */
void pop_guard_enter(uint8_t room);
/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE,
* как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c
* (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */
void pop_guard_heal(void) __banked;
void pop_guard_draw(void) __banked;
/* Убрать стража со сцены (смена комнаты / инициализация). */
void pop_guard_reset(void);
/* Чит «убить стража» (seg000:786, K): скелета не берёт. Вызывать только
* при включённых читах — проверку делает вызывающий. */
void pop_guard_kill(void);
/* Тик логики стража — В БАНКЕ (guards.c). Состояние берёт отсюда. */
void pop_guard_tick(void) __banked;
#endif
+228 -49
View File
@@ -2,28 +2,26 @@
* pop_kid.c — движок Kid (Фаза K1): порт seg006.c play_seq/load_frame + * pop_kid.c — движок Kid (Фаза K1): порт seg006.c play_seq/load_frame +
* seqtbl-интерпретатор. Данные — kid_data.h. * seqtbl-интерпретатор. Данные — kid_data.h.
*/ */
#include <fcntl.h>
#include <unistd.h>
#include <sprinter.h>
#include <sprinter_mem.h>
#include <graphics.h> #include <graphics.h>
#include <gfx.h> #include <gfx.h>
#include <sprite.h> #include <sprite.h>
#include "pop_kid.h" #include "pop_kid.h"
#include "pop_guard.h" /* Guard/pop_gframe/CHARID_* — интерпретатор общий */
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */ #include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
#include "pop_map.h" /* pop_clip_char_top (clip_char) */ #include "pop_map.h" /* pop_clip_char_top (clip_char) */
#include "pop_geom.h"
#include "kid_data.h" #include "kid_data.h"
kid_t Kid; pop_char_t Kid;
/* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN /* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN
* в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */ * в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */
int8_t knock; int8_t knock;
/* Координатные таблицы (data.h). */
static const uint8_t x_bump[20] = {
(uint8_t)-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
};
static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
#define FIRST_ONSCREEN_COLUMN 5
#define TILE_SIZEX 14
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */ /* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */
#define KID_MAXPAGES 28 #define KID_MAXPAGES 28
@@ -44,7 +42,7 @@ static uint8_t sword_ok;
int pop_sword_load(void) int pop_sword_load(void)
{ {
if (atlas_load(&swordp, "sword.atl") != 0) return -1; if (atlas_load(&swordp, "KID\\sword.atl") != 0) return -1;
sword_ok = 1; sword_ok = 1;
return 0; return 0;
} }
@@ -52,11 +50,11 @@ int pop_sword_load(void)
int pop_kid_load(uint8_t npages) int pop_kid_load(uint8_t npages)
{ {
static const char *const names[KID_MAXPAGES] = { static const char *const names[KID_MAXPAGES] = {
"kid0.atl","kid1.atl","kid2.atl","kid3.atl","kid4.atl","kid5.atl", "KID\\kid0.atl","KID\\kid1.atl","KID\\kid2.atl","KID\\kid3.atl","KID\\kid4.atl","KID\\kid5.atl",
"kid6.atl","kid7.atl","kid8.atl","kid9.atl","kid10.atl","kid11.atl", "KID\\kid6.atl","KID\\kid7.atl","KID\\kid8.atl","KID\\kid9.atl","KID\\kid10.atl","KID\\kid11.atl",
"kid12.atl","kid13.atl","kid14.atl","kid15.atl","kid16.atl","kid17.atl", "KID\\kid12.atl","KID\\kid13.atl","KID\\kid14.atl","KID\\kid15.atl","KID\\kid16.atl","KID\\kid17.atl",
"kid18.atl","kid19.atl","kid20.atl","kid21.atl","kid22.atl","kid23.atl", "KID\\kid18.atl","KID\\kid19.atl","KID\\kid20.atl","KID\\kid21.atl","KID\\kid22.atl","KID\\kid23.atl",
"kid24.atl","kid25.atl","kid26.atl","kid27.atl" "KID\\kid24.atl","KID\\kid25.atl","KID\\kid26.atl","KID\\kid27.atl"
}; };
uint8_t i; uint8_t i;
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES; if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
@@ -75,63 +73,215 @@ void pop_kid_free(void)
kid_npages = 0; kid_npages = 0;
} }
/* ---- seqtbl-доступ (база SEQTBL_BASE) ------------------------------ */ /* ---- Данные анимации в EMM-странице (kid_data.bin) ------------------ *
#define SEQ(cs) kid_seqtbl[(uint16_t)((cs) - SEQTBL_BASE)] * Таблица кадров (241*5 Б) и seqtbl (2310 Б) занимали 3.5 КБ в _CODE окна
* W1/W2 — самый дефицитный ресурс. Теперь они лежат в отдельной EMM-
* странице, которая маппится в W0 РОВНО на время play_seq (раз за тик), а
* весь остальной код читает cur_frame — 5-байтовую копию текущего кадра.
* Это ровно устройство оригинала: load_frame (seg006) заполняет cur_frame,
* и коллизия/отрисовка работают с ним, а не с таблицей.
* Страница адресуется как у pop_level: данные с 0x100, ниже — ISR-стаб. */
extern void _gfx_w0_isr(void);
#define KD_DATA_OFF 0x100u
/* Адрес кадра i в замапленной странице. Считаем В uint16_t и кастуем ОДИН
* раз: запись `(const uint8_t *)CONST + (uint16_t)i * 5u` SDCC 4.5 собирает
* НЕВЕРНО — умножение делает в 16 битах (add hl,hl / add hl,bc), а потом
* берёт только МЛАДШИЙ байт результата и подставляет старший байт константы
* (`ld c,l` / `inc b`). Для кадров i >= 52 (i*5 >= 256) адрес уезжает на
* 256*k, и cur_frame наполняется чужой строкой таблицы: у бега/шага/подъёма
* пропадал бит FRAME_NEEDS_FLOOR, и Kid проваливался сквозь пол.
* Артефакт: .sprinter-cc-roomtest/pop_kid.asm, функция load_frame. */
static const uint8_t *kd_frame_ptr(uint8_t i)
{
uint16_t off = (uint16_t)i;
off += (uint16_t)(off << 2); /* i*5 = i + i*4, без умножения */
off += (uint16_t)(KD_DATA_OFF + KID_BIN_FRAMES_OFF);
return (const uint8_t *)off;
}
/* То же для таблицы кадров СТРАЖА (frame_tbl_guard дописана в ту же
* страницу данных, см. pop_extract_kid_data.py). */
static const uint8_t *kd_gframe_ptr(uint8_t i)
{
uint16_t off = (uint16_t)i;
off += (uint16_t)(off << 2);
off += (uint16_t)(KD_DATA_OFF + KID_BIN_GFRAMES_OFF);
return (const uint8_t *)off;
}
#define KD_SEQ(cs) (*((const uint8_t *)(KD_DATA_OFF + KID_BIN_SEQTBL_OFF) + \
(uint16_t)((cs) - SEQTBL_BASE)))
/* Байт seqtbl по адресу оригинала (база SEQTBL_BASE); ТОЛЬКО под замапленной
* страницей данных — то есть внутри play_seq. */
#define SEQ(cs) KD_SEQ(cs)
static uint8_t kdat_blk, kdat_page, kdat_ok;
/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы.
* Заполняется load_frame для АКТИВНОГО персонажа (Char), поэтому на
* выходе из play_seq его надо сохранить в кэш своего персонажа: страж
* тикает ПОСЛЕ Кида, и без разделения kid_draw взял бы кадр стража. */
static kframe cur_frame;
static kframe kid_frame; /* кадр Kid для kid_draw */
/* Char (seg006): АКТИВНЫЙ персонаж. Интерпретатор последовательностей в
* оригинале работает не с Kid и не с Guard, а с этой копией, а вокруг
* стоят loadkid/savekid и loadshad/saveshad (seg006:809..825). Копия, а
* не указатель, — и так же, как в оригинале, и быстрее на Z80: обращения
* в горячем цикле идут абсолютной адресацией глобала, а 16-байтовое
* копирование платится один раз на переключение персонажа. */
pop_char_t Char;
void pop_loadkid(void) { Char = Kid; }
void pop_savekid(void) { Kid = Char; kid_frame = cur_frame; }
/* Выгрузка БЕЗ кадра: control() кадр не трогает, а cur_frame в этот
* момент принадлежит тому, кто последним крутил play_seq. */
void pop_savekid_state(void) { Kid = Char; }
void pop_loadshad(void) { Char = Guard; }
void pop_saveshad(void) { Guard = Char; pop_gframe = cur_frame; }
int pop_kid_data_load(const char *path)
{
uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */
uint8_t saved_w3, blk;
uint16_t stub;
int fd, n;
fd = open(path, O_RDONLY);
if (fd < 0) return -1;
blk = mem_alloc_pages(1);
if (!blk) { close(fd); return -1; }
kdat_blk = blk;
kdat_page = mem_get_page(blk, 0);
saved_w3 = _io_page_w3;
sprinter_page_w3(kdat_page);
n = read(fd, pg + KD_DATA_OFF, 16384 - KD_DATA_OFF);
if (n >= (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) {
stub = (uint16_t)&_gfx_w0_isr; /* ISR-стаб W0-страницы (как pop_level) */
pg[0x38] = 0xC3;
pg[0x39] = (uint8_t)(stub & 0xFF);
pg[0x3A] = (uint8_t)(stub >> 8);
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN */
}
sprinter_page_w3(saved_w3);
close(fd);
if (n < (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { mem_free_block(blk); return -1; }
kdat_ok = 1;
return 0;
}
void pop_kid_data_free(void)
{
if (kdat_ok) { mem_free_block(kdat_blk); kdat_ok = 0; }
}
/* Прочитать кадр из ЛЮБОЙ таблицы страницы данных (у Kid и у стража они
* разные: KID_BIN_FRAMES_OFF / KID_BIN_GFRAMES_OFF). Сама маппит страницу,
* поэтому годится для вызова извне (pop_guard) — но НЕ в горячем цикле. */
void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5)
{
const uint8_t *f;
uint16_t off = (uint16_t)idx;
if (!kdat_ok) { out5[0] = 255; return; }
off += (uint16_t)(off << 2); /* idx*5, без умножения */
off += (uint16_t)(KD_DATA_OFF + tbl_off);
gfx_w0_map(kdat_page);
f = (const uint8_t *)off;
out5[0] = f[0]; out5[1] = f[1]; out5[2] = f[2];
out5[3] = f[3]; out5[4] = f[4];
gfx_w0_unmap();
}
/* load_frame (seg006:0293): скопировать кадр Kid.frame из таблицы в
* cur_frame. Звать ТОЛЬКО когда страница данных замаплена в W0. */
static void load_frame(void)
{
/* Таблица кадров зависит от персонажа (seg006:0293): у Kid своя, у
* стража frame_tbl_guard с индексом frame + add_frame 149, где
* add_frame = 70 для кадров 102..106 (seg006:535). */
const uint8_t *f;
if (Char.charid == CHARID_2_GUARD) {
int16_t idx = (int16_t)Char.frame;
if (idx >= 102 && idx < 107) idx += 70;
idx -= 149;
if (idx < 0 || idx >= KID_NGFRAMES) { cur_frame.image = 255; return; }
f = kd_gframe_ptr((uint8_t)idx);
} else {
f = kd_frame_ptr(Char.frame);
}
cur_frame.image = f[0];
cur_frame.dx = (int8_t)f[1];
cur_frame.dy = (int8_t)f[2];
cur_frame.flags = f[3];
cur_frame.sword = f[4];
}
static uint8_t char_dx_forward(int8_t dx) static uint8_t char_dx_forward(int8_t dx)
{ {
if (Kid.direction < 0) dx = (int8_t)-dx; if (Char.direction < 0) dx = (int8_t)-dx;
return (uint8_t)(Kid.x + dx); return (uint8_t)(Char.x + dx);
} }
/* Аксессоры к текущему кадру (для pop_map — без дублирования kid_frames). */ /* Аксессоры к текущему кадру (для pop_map): читают cur_frame, НЕ таблицу —
int8_t kid_cur_dx(void) { return kid_frames[Kid.frame].dx; } * их дёргают несколько раз за кадр, маппинг страницы тут недопустим. */
uint8_t kid_cur_flags(void) { return kid_frames[Kid.frame].flags; } int8_t kid_cur_dx(void) { return cur_frame.dx; }
uint8_t kid_cur_flags(void) { return cur_frame.flags; }
/* seqtbl_offset_char: сменить последовательность Kid (единая точка — /* seqtbl_offset_char: сменить последовательность Kid (единая точка —
* pop_ctrl/pop_map зовут ЕЁ, а не дублируют kid_seq_off/kid_seqtbl). */ * pop_ctrl/pop_map зовут ЕЁ, а не дублируют kid_seq_off/kid_seqtbl). */
void kid_set_seq(uint8_t seq_id) { Kid.curr_seq = kid_seq_off[seq_id]; } void kid_set_seq(uint8_t seq_id) { Kid.curr_seq = kid_seq_off[seq_id]; }
/* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0). */ /* То же для АКТИВНОГО персонажа — внутри окна Char (порт
* seqtbl_offset_char, seg006). Нужен стражу: его последовательность
* ставится, когда Char уже загружен из Guard. */
void pop_char_set_seq(uint8_t seq_id) { Char.curr_seq = kid_seq_off[seq_id]; }
/* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0).
* Страница данных маппится в W0 на ВЕСЬ разбор — один map/unmap за вызов
* (в тике, до отрисовки: конфликта с атласом в W0 нет). В конце —
* load_frame: пока страница ещё замаплена, копируем кадр в cur_frame. */
void play_seq(void) void play_seq(void)
{ {
if (!kdat_ok) return;
gfx_w0_map(kdat_page);
for (;;) { for (;;) {
uint8_t cmd = SEQ(Kid.curr_seq); uint8_t cmd = SEQ(Char.curr_seq);
Kid.curr_seq++; Char.curr_seq++;
switch (cmd) { switch (cmd) {
case 0xFB: /* DX */ case 0xFB: /* DX */
Kid.x = char_dx_forward((int8_t)SEQ(Kid.curr_seq)); Char.x = char_dx_forward((int8_t)SEQ(Char.curr_seq));
Kid.curr_seq++; Char.curr_seq++;
break; break;
case 0xFA: /* DY */ case 0xFA: /* DY */
Kid.y = (uint8_t)(Kid.y + (int8_t)SEQ(Kid.curr_seq)); Char.y = (uint8_t)(Char.y + (int8_t)SEQ(Char.curr_seq));
Kid.curr_seq++; Char.curr_seq++;
break; break;
case 0xFE: /* FLIP */ case 0xFE: /* FLIP */
Kid.direction = (int8_t)~Kid.direction; Char.direction = (int8_t)~Char.direction;
break; break;
case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */ case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */
Kid.curr_seq += 2; Char.curr_seq += 2;
break; break;
case 0xFF: { /* JMP */ case 0xFF: { /* JMP */
uint16_t a = (uint16_t)(SEQ(Kid.curr_seq) | uint16_t a = (uint16_t)(SEQ(Char.curr_seq) |
((uint16_t)SEQ((uint16_t)(Kid.curr_seq + 1)) << 8)); ((uint16_t)SEQ((uint16_t)(Char.curr_seq + 1)) << 8));
Kid.curr_seq = a; Char.curr_seq = a;
break; break;
} }
case 0xFD: /* UP */ case 0xFD: /* UP */
Kid.curr_row--; Char.curr_row--;
break; break;
case 0xFC: /* DOWN */ case 0xFC: /* DOWN */
Kid.curr_row++; Char.curr_row++;
break; break;
case 0xF9: /* ACTION */ case 0xF9: /* ACTION */
Kid.action = SEQ(Kid.curr_seq); Char.action = SEQ(Char.curr_seq);
Kid.curr_seq++; Char.curr_seq++;
break; break;
case 0xF8: /* SET_FALL */ case 0xF8: /* SET_FALL */
Kid.fall_x = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++; Char.fall_x = (int8_t)SEQ(Char.curr_seq); Char.curr_seq++;
Kid.fall_y = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++; Char.fall_y = (int8_t)SEQ(Char.curr_seq); Char.curr_seq++;
break; break;
case 0xF5: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */ case 0xF5: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */
knock = 1; knock = 1;
@@ -140,41 +290,50 @@ void play_seq(void)
knock = -1; knock = -1;
break; break;
case 0xF2: /* SOUND — потребить 1 байт */ case 0xF2: /* SOUND — потребить 1 байт */
Kid.curr_seq++; Char.curr_seq++;
break; break;
case 0xF1: /* END_LEVEL */ case 0xF1: /* END_LEVEL */
break; break;
case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета
* (порт play_seq -> proc_get_object, seg006) */ * (порт play_seq -> proc_get_object, seg006) */
if (SEQ(Kid.curr_seq) == 1) if (SEQ(Char.curr_seq) == 1)
pop_proc_get_object(); pop_proc_get_object();
Kid.curr_seq++; Char.curr_seq++;
break; break;
case 0xF6: /* DIE nop */ case 0xF6: /* DIE nop */
break; break;
default: /* КАДР */ default: /* КАДР */
Kid.frame = cmd; Char.frame = cmd;
load_frame(); /* cur_frame := таблица[frame] (страница ещё в W0) */
gfx_w0_unmap();
return; return;
} }
} }
} }
/* Прокрутить последовательность КОНКРЕТНОГО персонажа (порт связки
* loadkid/play_seq/savekid, seg006). Звать вместо голого play_seq: тот
* работает с Char и сам по себе не знает, чей это персонаж. */
void pop_kid_play(void) { pop_loadkid(); play_seq(); pop_savekid(); }
void pop_guard_play(void) { pop_loadshad(); play_seq(); pop_saveshad(); }
void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir) void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir)
{ {
Kid.curr_col = col; Kid.curr_col = col;
Kid.curr_row = row; Kid.curr_row = row;
Kid.direction = dir; Kid.direction = dir;
Kid.x = (uint8_t)(x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX); Kid.x = (uint8_t)(pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
Kid.y = (uint8_t)y_land[row + 1]; Kid.y = (uint8_t)pop_y_land[row + 1];
Kid.fall_x = Kid.fall_y = 0; Kid.fall_x = Kid.fall_y = 0;
Kid.repeat = 0; Kid.repeat = 0;
Kid.charid = 0; /* charid_0_kid: своя таблица кадров */
Kid.curr_seq = kid_seq_off[seq_id]; Kid.curr_seq = kid_seq_off[seq_id];
play_seq(); /* до первого кадра */ pop_kid_play(); /* до первого кадра */
} }
void kid_tick(void) void kid_tick(void)
{ {
play_seq(); pop_kid_play();
} }
/* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy /* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy
@@ -263,6 +422,9 @@ void kid_draw_splash(void)
if (top + (int)h > y1) y1 = top + (int)h; if (top + (int)h > y1) y1 = top + (int)h;
kid_lx[dp] = x0; kid_ly[dp] = y0; kid_lx[dp] = x0; kid_ly[dp] = y0;
kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0); kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0);
/* Окно fore-слоя расширяем тем же объединением: брызги
* тоже должны уходить под передние грани тайлов. */
pop_fore_set_clip(x0, y0 + POP_YOFF, x1 - x0, y1 - y0);
} }
} }
} }
@@ -280,12 +442,16 @@ void kid_heal(void)
void kid_draw(void) void kid_draw(void)
{ {
kframe fr = kid_frames[Kid.frame]; kframe fr = kid_frame; /* кадр Kid, сохранённый savekid */
int fwd, obj_x, obj_y, top; int fwd, obj_x, obj_y, top;
uint8_t page, idx; uint8_t page, idx;
const uint8_t *img; const uint8_t *img;
uint16_t w, h; uint16_t w, h;
/* Окно fore-слоя по умолчанию пустое: на любом раннем выходе (нет
* кадра / страница не загружена) перекрывать нечего, и fore-проход
* не должен трогать экран. */
pop_fore_set_clip(0, 0, 0, 0);
if (fr.image == 255) if (fr.image == 255)
return; /* пустой кадр */ return; /* пустой кадр */
/* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠ /* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠
@@ -384,4 +550,17 @@ void kid_draw(void)
} }
} }
gfx_w0_unmap(); gfx_w0_unmap();
/* Окно fore-слоя = итоговый прямоугольник спрайта (с клинком и
* обрезкой clip_char): fore-проход вернёт куски тайлов ТОЛЬКО поверх
* него, а не целыми тайлами — см. pop_bg.c, fclip_on. Ставим и когда
* рисовать нечего (kid_lvalid=0): нулевое окно = fore-проход не
* трогает экран, что и правильно — перекрывать нечего. */
{
uint8_t dp = gfx_get_draw_page() & 1;
if (kid_lvalid[dp])
pop_fore_set_clip(kid_lx[dp], kid_ly[dp] + POP_YOFF,
(int)kid_lw[dp], (int)kid_lh[dp]);
else
pop_fore_set_clip(0, 0, 0, 0);
}
} }
+32 -14
View File
@@ -11,20 +11,10 @@
#define POP_KID_H #define POP_KID_H
#include <stdint.h> #include <stdint.h>
#include "pop_char.h"
/* Состояние персонажа (подмножество char_type, types.h). */ /* Состояние персонажа — общий тип для Кида, стража и Char (pop_char.h). */
typedef struct { extern pop_char_t Kid;
uint8_t frame;
uint8_t x, y;
int8_t direction; /* -1 влево, 0 вправо */
int8_t curr_col, curr_row;
uint8_t action;
int8_t fall_x, fall_y;
uint8_t repeat;
uint16_t curr_seq; /* адрес в seqtbl (база SEQTBL_BASE) */
} kid_t;
extern kid_t Kid;
/* Флаг сотрясения (seg006 knock): +1 = KNOCK_UP (ряд выше), -1 = KNOCK_DOWN /* Флаг сотрясения (seg006 knock): +1 = KNOCK_UP (ряд выше), -1 = KNOCK_DOWN
* (свой ряд). Ставит play_seq, читает/сбрасывает pop_check_knock. */ * (свой ряд). Ставит play_seq, читает/сбрасывает pop_check_knock. */
@@ -34,6 +24,18 @@ extern int8_t knock;
* 0 — OK, -1 — ошибка. */ * 0 — OK, -1 — ошибка. */
int pop_kid_load(uint8_t npages); int pop_kid_load(uint8_t npages);
/* Данные анимации (таблица кадров + seqtbl) — отдельная EMM-страница
* (kid_data.bin): в _CODE окна W1/W2 они занимали 3.5 КБ. Грузить ДО
* первого kid_init/play_seq. 0 — OK, -1 — ошибка (без них Kid не оживёт).
* Страница маппится в W0 внутри play_seq, раз за тик. */
int pop_kid_data_load(const char *path);
/* Прочитать кадр из таблицы страницы данных: tbl_off — KID_BIN_FRAMES_OFF
* (Kid) или KID_BIN_GFRAMES_OFF (страж, своя таблица frame_tbl_guard).
* Маппит страницу сама; для горячего цикла есть cur_frame. */
void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5);
void pop_kid_data_free(void);
/* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах /* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах
* подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798). * подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798).
* Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда * Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда
@@ -51,7 +53,23 @@ void kid_tick(void);
/* play_seq напрямую (для pop_map start_fall — переход в fall-seq в тот же /* play_seq напрямую (для pop_map start_fall — переход в fall-seq в тот же
* physics-шаг, как оригинал). */ * physics-шаг, как оригинал). */
void play_seq(void); void play_seq(void); /* работает с Char — см. pop_kid_play ниже */
/* Активный персонаж (порт Char, seg006) и его загрузка/выгрузка.
* Интерпретатор последовательностей общий для Kid и стража: у них общая
* seqtbl, но РАЗНЫЕ таблицы кадров (load_frame выбирает по Char.charid). */
extern pop_char_t Char;
void pop_loadkid(void);
void pop_savekid(void);
void pop_savekid_state(void); /* Kid = Char, кадр не трогаем */
void pop_loadshad(void);
void pop_saveshad(void);
/* Прокрутить последовательность персонажа целиком (load + play_seq +
* save). Звать вместо голого play_seq. */
void pop_char_set_seq(uint8_t seq_id); /* seq активного персонажа */
void pop_kid_play(void);
void pop_guard_play(void);
/* Поля текущего кадра kid_frames[Kid.frame] (pop_map, без дублирования /* Поля текущего кадра kid_frames[Kid.frame] (pop_map, без дублирования
* таблицы во втором TU). */ * таблицы во втором TU). */
+28
View File
@@ -23,6 +23,12 @@ extern void _gfx_w0_isr(void);
#define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */ #define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */
#define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */ #define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */
#define BP_LINKS 1952u #define BP_LINKS 1952u
/* Стражи (level_type, types.h:228): по байту на комнату в каждом массиве. */
#define BP_GTILE 2119u
#define BP_GDIR 2143u
#define BP_GX 2167u
#define BP_GSKILL 2215u
#define BP_GCOLOR 2263u
#define BP_START 2112u #define BP_START 2112u
#define ROOM_TILES 30u #define ROOM_TILES 30u
#define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */ #define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */
@@ -234,6 +240,28 @@ static uint8_t start_byte(uint16_t off)
return v; return v;
} }
/* Страж комнаты (enter_guard, seg002:0112 + pos_guards, seg003). Возврат
* 0 = стража в комнате нет (tile >= 30). X оригинал пересчитывает из
* колонки (pos_guards), поэтому guards_x из файла не читаем. */
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
uint8_t *color, uint8_t *skill)
{
const uint8_t *p;
uint8_t t;
if (!lvl_ok || room < 1 || room > 24) return 0;
gfx_w0_map(lvl_page);
p = (const uint8_t *)LVL_DATA_OFF;
t = p[BP_GTILE + room - 1];
if (t < 30) {
*tile = t;
*dir = (int8_t)p[BP_GDIR + room - 1];
*color = (uint8_t)(p[BP_GCOLOR + room - 1] & 0x0F);
*skill = p[BP_GSKILL + room - 1];
}
gfx_w0_unmap();
return (uint8_t)(t < 30);
}
uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); } uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); }
uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); } uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); }
int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); } int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); }
+5
View File
@@ -73,6 +73,11 @@ void pop_doorlink2_set(uint8_t i, uint8_t v);
int8_t pop_room_col_landing(uint8_t room, uint8_t col); int8_t pop_room_col_landing(uint8_t room, uint8_t col);
/* Стартовая позиция уровня (блок @2112): комната / поза / направление. */ /* Стартовая позиция уровня (блок @2112): комната / поза / направление. */
/* Страж комнаты: 1 = есть (заполнит tile/dir/color/skill), 0 = нет.
* Порт enter_guard (seg002:0112). */
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
uint8_t *color, uint8_t *skill);
uint8_t pop_level_start_room(void); uint8_t pop_level_start_room(void);
uint8_t pop_level_start_pos(void); uint8_t pop_level_start_pos(void);
int8_t pop_level_start_dir(void); int8_t pop_level_start_dir(void);
+82 -114
View File
@@ -5,7 +5,7 @@
* FRAME_NEEDS_FLOOR (0x40), под персонажем не floor -> start_fall(); * FRAME_NEEDS_FLOOR (0x40), под персонажем не floor -> start_fall();
* стена -> in_wall(); * стена -> in_wall();
* freefall (action 4) -> do_fall(): fall_y-ускорение (в fall_accel/speed), * freefall (action 4) -> do_fall(): fall_y-ускорение (в fall_accel/speed),
* достиг y_land[curr_row+1] -> land()/inc_curr_row. * достиг pop_y_land[curr_row+1] -> land()/inc_curr_row.
* *
* Координатная система PoP: экран.left = 58, тайл = 14 ед. (TILE_SIZEX); * Координатная система PoP: экран.left = 58, тайл = 14 ед. (TILE_SIZEX);
* col = (xpos-58)/14 (округл. вниз), xl = остаток; get_tile_div_mod_m7 = * col = (xpos-58)/14 (округл. вниз), xl = остаток; get_tile_div_mod_m7 =
@@ -19,7 +19,16 @@
#include "pop_kid.h" #include "pop_kid.h"
#include "pop_map.h" #include "pop_map.h"
#include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */ #include "pop_ctrl.h" /* pop_ctrl_shift_held() — для check_grab */
#include "pop_bg.h" /* pop_loose_shake_draw/bake_empty — перерисовка */ #include "pop_geom.h" /* общая геометрия (x_bump/y_land/y_to_row) */
#include "pop_redraw.h" /* пометки перерисовки (порт set_redraw_*) */
/* pop_bg нужен ТОЛЬКО падающему куску (mob): spawn/tick/pos — это движущийся
* ОБЪЕКТ, а не перерисовка тайла (в оригинале он и живёт отдельно: mobs +
* draw_moving). Из-за него pop_loose_tick — единственная функция модуля,
* которую нельзя звать из __banked-кода; вся коллизия/физика/предметы, то
* есть то, что понадобится стражам, чиста от графики. Разделение mob на
* логику и отрисовку — задел следующей фазы. */
#include "pop_bg.h"
#include "pop_state.h"
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */ #include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */ /* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
@@ -81,23 +90,17 @@
#define FRAME_NEEDS_FLOOR 0x40 #define FRAME_NEEDS_FLOOR 0x40
#define FRAME_WEIGHT_X 0x1F #define FRAME_WEIGHT_X 0x1F
#define TILE_SIZEX 14
#define TILE_RIGHTX 13 #define TILE_RIGHTX 13
#define SCREENSPACE_X 58 #define SCREENSPACE_X 58
#define FALL_ACCEL 3 #define FALL_ACCEL 3
#define FALL_MAX 33 #define FALL_MAX 33
#define TILE_MIDX 7 #define TILE_MIDX 7
#define FIRST_ONSCREEN_COLUMN 5
#define FRAME_THIN 0x20 #define FRAME_THIN 0x20
static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
static const int8_t dir_front[2] = {-1, 1}; /* [dir+1]: dir=-1->-1, 0->+1 */ static const int8_t dir_front[2] = {-1, 1}; /* [dir+1]: dir=-1->-1, 0->+1 */
static const int8_t dir_behind[2] = {1, -1}; /* [dir+1]: назад по направлению */ static const int8_t dir_behind[2] = {1, -1}; /* [dir+1]: назад по направлению */
static const int16_t x_bump[20] = {
-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
};
/* wall_type(tile): gate/doortop=1, mirror=2, chomper=3, wall=4, иначе 0. */ /* wall_type(tile): gate/doortop=1, mirror=2, chomper=3, wall=4, иначе 0. */
static const int8_t wall_dl[6] = {0, 10, 0, -1, 0, 0}; /* wall_dist_from_left */ static const int8_t wall_dl[6] = {0, 10, 0, -1, 0, 0}; /* wall_dist_from_left */
static const int8_t wall_dr[6] = {0, 0, 10, 13, 0, 0}; /* wall_dist_from_right */ static const int8_t wall_dr[6] = {0, 0, 10, 13, 0, 0}; /* wall_dist_from_right */
@@ -142,20 +145,18 @@ static uint8_t g_lcol[6], g_rcol[6];
* отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. — * отдельное — pop_ceil_modif[col] с той же семантикой (0 покой, 0x80.. —
* тряска, 1..11 — отсчёт до падения). */ * тряска, 1..11 — отсчёт до падения). */
uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */ uint8_t pop_ceil_modif[10]; /* публично: читает pop_bg при отрисовке */
static uint8_t ceil_rest[10]; /* «перерисовать ПОКОЙ» (2 = обе страницы) */
static uint8_t ceil_bake[10]; /* «запечь колодец» на второй странице */
static uint8_t land_bake, land_bake_pos; /* щебень от приземления — вторая страница */
static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */ static uint8_t *g_above; /* ряд 2 комнаты сверху (fg), мутабельный */
uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */ uint8_t pop_ceil_fell; /* 0 = нет; иначе col+1 провалившейся плиты-потолка */
uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */ uint8_t pop_debris_at; /* 0 = нет; иначе tilepos+1 нового щебня в ЭТОЙ комнате */
void pop_map_set_above(uint8_t *above_row2_fg) { g_above = above_row2_fg; } void pop_map_set_above(uint8_t *above_row2_fg) __banked { g_above = above_row2_fg; }
void pop_map_set(uint8_t *fg) { g_fg = fg; } void pop_map_set(uint8_t *fg) __banked { g_fg = fg; }
void pop_map_set_room(uint8_t room) { g_room = room; } void pop_map_set_room(uint8_t room) __banked { g_room = room; }
void pop_kid_hp_reset(void) { hitp_curr = 3; pop_kid_dead = 0; } void pop_kid_hp_reset(void) __banked { hitp_curr = 3; pop_kid_dead = 0; }
/* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */ /* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */
static uint8_t take_hp(uint8_t count) static uint8_t take_hp(uint8_t count)
@@ -166,7 +167,7 @@ static uint8_t take_hp(uint8_t count)
} }
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down, void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
const uint8_t *lcol_fg, const uint8_t *rcol_fg) const uint8_t *lcol_fg, const uint8_t *rcol_fg) __banked
{ {
g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down; g_link_l = left; g_link_r = right; g_link_u = up; g_link_d = down;
if (lcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_lcol[i] = lcol_fg[i]; } if (lcol_fg) { uint8_t i; for (i = 0; i < 6; i++) g_lcol[i] = lcol_fg[i]; }
@@ -367,7 +368,7 @@ static int dist_from_wall_forward(uint8_t tiletype, int8_t tcol)
uint8_t type = wall_type(tiletype); uint8_t type = wall_type(tiletype);
int coll_left, cx; int coll_left, cx;
if (type == 0) return 127; if (type == 0) return 127;
coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX; coll_left = pop_x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
cx = char_front_coll(); cx = char_front_coll();
if (Kid.direction < 0) /* лицом влево */ if (Kid.direction < 0) /* лицом влево */
return cx - (coll_left + TILE_RIGHTX - wall_dr[type]); return cx - (coll_left + TILE_RIGHTX - wall_dr[type]);
@@ -423,7 +424,7 @@ static uint8_t fell_on_spikes(void)
static void land(void) static void land(void)
{ {
uint8_t seq; uint8_t seq;
Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
if (fell_on_spikes()) return; /* упал на вредные пики — смерть */ if (fell_on_spikes()) return; /* упал на вредные пики — смерть */
/* к краю пола — чуть назад (как оригинал) */ /* к краю пола — чуть назад (как оригинал) */
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
@@ -441,7 +442,7 @@ static void land(void)
if (deadly || take_hp(1)) { /* 3+ этажа или последнее HP */ if (deadly || take_hp(1)) { /* 3+ этажа или последнее HP */
take_hp(100); take_hp(100);
kid_set_seq(SEQ_22_CRUSHED); kid_set_seq(SEQ_22_CRUSHED);
play_seq(); pop_kid_play();
determine_col(); determine_col();
pop_kid_dead = 1; pop_kid_dead = 1;
return; return;
@@ -450,7 +451,7 @@ static void land(void)
} }
Kid.fall_x = Kid.fall_y = 0; Kid.fall_x = Kid.fall_y = 0;
kid_set_seq(seq); kid_set_seq(seq);
play_seq(); pop_kid_play();
determine_col(); determine_col();
} }
@@ -464,7 +465,7 @@ static void start_fall(void)
else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP; else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP;
else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */ else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */
kid_set_seq(seq); kid_set_seq(seq);
play_seq(); pop_kid_play();
determine_col(); determine_col();
/* Проверки «упал в стену» — ТОЛЬКО пока персонаж В комнате (curr_row<=2). /* Проверки «упал в стену» — ТОЛЬКО пока персонаж В комнате (curr_row<=2).
* Ниже нижнего ряда (curr_row=3 после inc — падение сквозь дыру loose) * Ниже нижнего ряда (curr_row=3 после inc — падение сквозь дыру loose)
@@ -518,7 +519,7 @@ static void check_grab(void)
uint8_t old_x; uint8_t old_x;
if (!pop_ctrl_shift_held()) return; /* Shift не зажат */ if (!pop_ctrl_shift_held()) return; /* Shift не зажат */
if ((uint8_t)Kid.fall_y >= 32) return; /* падает слишком быстро */ if ((uint8_t)Kid.fall_y >= 32) return; /* падает слишком быстро */
if ((uint16_t)y_land[Kid.curr_row + 1] > (uint16_t)(Kid.y + 25)) return; if ((uint16_t)pop_y_land[Kid.curr_row + 1] > (uint16_t)(Kid.y + 25)) return;
old_x = Kid.x; old_x = Kid.x;
Kid.x = (uint8_t)char_dx_forward(-8); Kid.x = (uint8_t)char_dx_forward(-8);
determine_col(); determine_col();
@@ -527,10 +528,10 @@ static void check_grab(void)
determine_col(); determine_col();
} else { } else {
Kid.x = (uint8_t)char_dx_forward((int8_t)distance_to_edge_weight()); Kid.x = (uint8_t)char_dx_forward((int8_t)distance_to_edge_weight());
Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
Kid.fall_y = 0; Kid.fall_y = 0;
kid_set_seq(SEQ_15_GRAB_LEDGE_MIDAIR); kid_set_seq(SEQ_15_GRAB_LEDGE_MIDAIR);
play_seq(); pop_kid_play();
determine_col(); determine_col();
grab_timer = 12; /* (TODO: sound grab) */ grab_timer = 12; /* (TODO: sound grab) */
} }
@@ -539,8 +540,8 @@ static void check_grab(void)
static void do_fall(void) static void do_fall(void)
{ {
uint8_t nrow = (uint8_t)(Kid.curr_row + 1); uint8_t nrow = (uint8_t)(Kid.curr_row + 1);
if (nrow > 4) nrow = 4; /* защита y_land[] от выхода */ if (nrow > 4) nrow = 4; /* защита pop_y_land[] от выхода */
if ((uint16_t)y_land[nrow] > (uint16_t)Kid.y) { if ((uint16_t)pop_y_land[nrow] > (uint16_t)Kid.y) {
check_grab(); /* ещё летит — попытка зацепа */ check_grab(); /* ещё летит — попытка зацепа */
} else { } else {
if (get_tile_at_char() == TILE_WALL) if (get_tile_at_char() == TILE_WALL)
@@ -587,7 +588,7 @@ static int edge_classify(int d)
return d; return d;
} }
int pop_edge_distance(void) int pop_edge_distance(void) __banked
{ {
uint8_t t; int d; uint8_t t; int d;
determine_col(); determine_col();
@@ -629,7 +630,7 @@ int pop_edge_distance(void)
return distance_to_edge_weight(); return distance_to_edge_weight();
} }
uint8_t pop_edge_type(void) { return g_edge_type; } uint8_t pop_edge_type(void) __banked { return g_edge_type; }
/* ---- Прыжок вверх с зацепом (seg005 check_jump_up и производные) --- * /* ---- Прыжок вверх с зацепом (seg005 check_jump_up и производные) --- *
* pop_jump_up_seq выбирает seq для ↑ в стойке: чистый прыжок (seq_28 в * pop_jump_up_seq выбирает seq для ↑ в стойке: чистый прыжок (seq_28 в
@@ -682,7 +683,7 @@ static uint8_t jump_up_or_grab(void)
return grab_up_with_floor_behind(); return grab_up_with_floor_behind();
} }
uint8_t pop_jump_up_seq(void) uint8_t pop_jump_up_seq(void) __banked
{ {
uint8_t through = get_tile_above_char(); uint8_t through = get_tile_above_char();
get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */ get_tile_front_above_char(); /* g_curr_tile = спереди-сверху */
@@ -697,7 +698,7 @@ uint8_t pop_jump_up_seq(void)
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига — /* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
* позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */ * позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */
int pop_wall_ahead(void) int pop_wall_ahead(void) __banked
{ {
int d = pop_edge_distance(); int d = pop_edge_distance();
return (g_edge_type == EDGE_WALL && d <= 2); return (g_edge_type == EDGE_WALL && d <= 2);
@@ -708,7 +709,7 @@ int pop_wall_ahead(void)
* тайловые предикаты + выбор seq (+ выравнивание Kid.x). */ * тайловые предикаты + выбор seq (+ выравнивание Kid.x). */
/* grab_timer истёк — можно подтягиваться? */ /* grab_timer истёк — можно подтягиваться? */
uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; } uint8_t pop_hang_grab_ready(void) __banked { return grab_timer == 0; }
/* can_climb_up (seg005:09DF): тайл над персонажем — обычный подъём (seq_10) /* can_climb_up (seg005:09DF): тайл над персонажем — обычный подъём (seq_10)
* либо подъём к ЗАКРЫТОЙ калитке/зеркалу/чомперу (seq_73 = «влез и съехал * либо подъём к ЗАКРЫТОЙ калитке/зеркалу/чомперу (seq_73 = «влез и съехал
@@ -716,7 +717,7 @@ uint8_t pop_hang_grab_ready(void) { return grab_timer == 0; }
* curr_tile2 == tiles_4_gate && direction != right && modif >> 2 < 6 * curr_tile2 == tiles_4_gate && direction != right && modif >> 2 < 6
* — под поднятой решёткой (modif >> 2 >= 6) подъём обычный. Без проверки * — под поднятой решёткой (modif >> 2 >= 6) подъём обычный. Без проверки
* модификатора Kid срывался и с открытых ворот. */ * модификатора Kid срывался и с открытых ворот. */
uint8_t pop_hang_climb_up_seq(void) uint8_t pop_hang_climb_up_seq(void) __banked
{ {
uint8_t t = get_tile_above_char(); uint8_t t = get_tile_above_char();
if (((t == TILE_MIRROR || t == TILE_CHOMPER) && Kid.direction == 0) || if (((t == TILE_MIRROR || t == TILE_CHOMPER) && Kid.direction == 0) ||
@@ -728,7 +729,7 @@ uint8_t pop_hang_climb_up_seq(void)
/* Shift в висе: 1 = висит у стены/двери (caller играет seq_25); -1 = /* Shift в висе: 1 = висит у стены/двери (caller играет seq_25); -1 =
* сорваться (hang_fall); 0 = сверху пол, остаётся висеть (ничего). */ * сорваться (hang_fall); 0 = сверху пол, остаётся висеть (ничего). */
int8_t pop_hang_wall(void) int8_t pop_hang_wall(void) __banked
{ {
uint8_t t = get_tile_at_char(); uint8_t t = get_tile_at_char();
if (Kid.action != ACT_HANGSTRAIGHT && if (Kid.action != ACT_HANGSTRAIGHT &&
@@ -743,7 +744,7 @@ int8_t pop_hang_wall(void)
/* hang_fall (seg005:0A46): сорваться. Нет пола ни сзади, ни под /* hang_fall (seg005:0A46): сорваться. Нет пола ни сзади, ни под
* персонажем — seq_23 (отпустить и падать); иначе прижать к стене (-7) * персонажем — seq_23 (отпустить и падать); иначе прижать к стене (-7)
* и seq_11 (встать, конец спуска). Меняет Kid.x, возвращает seq. */ * и seq_11 (встать, конец спуска). Меняет Kid.x, возвращает seq. */
uint8_t pop_hang_fall_seq(void) uint8_t pop_hang_fall_seq(void) __banked
{ {
uint8_t behind = get_tile_behind_char(); uint8_t behind = get_tile_behind_char();
uint8_t at; uint8_t at;
@@ -760,7 +761,7 @@ uint8_t pop_hang_fall_seq(void)
* либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 = * либо спуск seq_68 через зацеп, либо отступ от передней кромки), 0 =
* приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки * приседать. Меняет Kid.x. Kid развёрнут от края (край сзади) у кромки
* пола + под соседним тайлом есть за что зацепиться → спуск на этаж вниз. */ * пола + под соседним тайлом есть за что зацепиться → спуск на этаж вниз. */
uint8_t pop_down_action(void) uint8_t pop_down_action(void) __banked
{ {
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) { if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3) {
Kid.x = (uint8_t)char_dx_forward(5); /* у передней кромки — отступ */ Kid.x = (uint8_t)char_dx_forward(5); /* у передней кромки — отступ */
@@ -805,7 +806,7 @@ static void do_pickup(int8_t obj_type, uint8_t tilepos)
/* proc_get_object (seg006:16CB): эффект поднятого предмета. Зовётся из /* proc_get_object (seg006:16CB): эффект поднятого предмета. Зовётся из
* play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */ * play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */
void pop_proc_get_object(void) void pop_proc_get_object(void) __banked
{ {
if (pickup_obj_type == -1) { if (pickup_obj_type == -1) {
pop_have_sword = 1; /* в оригинале have_sword = -1 */ pop_have_sword = 1; /* в оригинале have_sword = -1 */
@@ -821,7 +822,7 @@ void pop_proc_get_object(void)
/* check_get_item + get_item (seg005:061F/073E). Возврат: 0 — предмета нет /* check_get_item + get_item (seg005:061F/073E). Возврат: 0 — предмета нет
* (управление идёт обычной веткой); 1 — присесть (caller зовёт crouch()); * (управление идёт обычной веткой); 1 — присесть (caller зовёт crouch());
* 2 — поднять меч (caller ставит seq_91). */ * 2 — поднять меч (caller ставит seq_91). */
uint8_t pop_get_item_action(void) uint8_t pop_get_item_action(void) __banked
{ {
uint8_t t; uint8_t t;
int8_t icol; int8_t icol;
@@ -885,7 +886,7 @@ static void bumped_fall(void)
Kid.fall_x = 0; Kid.fall_x = 0;
} else { } else {
kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */ kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */
play_seq(); pop_kid_play();
} }
} }
@@ -900,11 +901,11 @@ static void bumped_floor(void)
uint8_t frame; uint8_t frame;
/* Оригинал сравнивает БЕЗЗНАКОВО: если персонаж НИЖЕ уровня пола, /* Оригинал сравнивает БЕЗЗНАКОВО: если персонаж НИЖЕ уровня пола,
* разность заворачивается в большое число и тоже даёт «падать». */ * разность заворачивается в большое число и тоже даёт «падать». */
if ((uint16_t)(y_land[Kid.curr_row + 1] - (int16_t)Kid.y) >= 15) { if ((uint16_t)(pop_y_land[Kid.curr_row + 1] - (int16_t)Kid.y) >= 15) {
bumped_fall(); /* высоко над полом — в воздухе */ bumped_fall(); /* высоко над полом — в воздухе */
return; return;
} }
Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; /* ПРИЖАТЬ к полу */ Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1]; /* ПРИЖАТЬ к полу */
if (Kid.fall_y >= 22) { /* влетел быстро — только отжать */ if (Kid.fall_y >= 22) { /* влетел быстро — только отжать */
Kid.x = (uint8_t)char_dx_forward(-5); Kid.x = (uint8_t)char_dx_forward(-5);
return; return;
@@ -919,7 +920,7 @@ static void bumped_floor(void)
kid_set_seq(SEQ_46_HARDBUMP); kid_set_seq(SEQ_46_HARDBUMP);
else else
kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */ kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */
play_seq(); pop_kid_play();
} }
/* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять /* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять
@@ -942,7 +943,7 @@ static void check_bumped(void)
* hang->hang_fall->seq_11 (правильное приземление). */ * hang->hang_fall->seq_11 (правильное приземление). */
if (Kid.frame >= 87 && Kid.frame < 100) return; if (Kid.frame >= 87 && Kid.frame < 100) return;
/* Вне рядов комнаты (падение мимо пола): get_tile даёт WALL, а /* Вне рядов комнаты (падение мимо пола): get_tile даёт WALL, а
* dist_from_wall_forward/x_bump[] — мусор с большим отриц. сдвигом → * dist_from_wall_forward/pop_x_bump[] — мусор с большим отриц. сдвигом →
* underflow X и уход в стену. fell_out ловит do_fall. */ * underflow X и уход в стену. fell_out ловит do_fall. */
if (Kid.curr_row < 0 || Kid.curr_row > 2) return; if (Kid.curr_row < 0 || Kid.curr_row > 2) return;
/* seg004 check_collisions: на кадрах разворота коллизии НЕ считаются /* seg004 check_collisions: на кадрах разворота коллизии НЕ считаются
@@ -973,7 +974,7 @@ static void check_bumped(void)
{ {
uint8_t f = 0, prev, need; uint8_t f = 0, prev, need;
if (wt) { if (wt) {
int coll_left = x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX; int coll_left = pop_x_bump[tcol + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
int lw = wall_dl[wt] + coll_left; /* get_left_wall_xpos */ int lw = wall_dl[wt] + coll_left; /* get_left_wall_xpos */
int rw = coll_left - wall_dr[wt] + TILE_RIGHTX; /* get_right_wall_xpos */ int rw = coll_left - wall_dr[wt] + TILE_RIGHTX; /* get_right_wall_xpos */
int e = char_x_forward_edge(), wh = ((int)kid_fp_width() + 1) >> 1; int e = char_x_forward_edge(), wh = ((int)kid_fp_width() + 1) >> 1;
@@ -1027,8 +1028,6 @@ static void check_bumped(void)
* отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/ * отсчёт до падения (на тайле стоят/пробили). Порт animate_loose/
* make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */ * make_loose_fall/check_press. Перерисовка — в pop_bg (shake/bake). */
uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */ uint8_t pop_loose_modif[30]; /* публично: читает pop_bg при отрисовке */
static uint8_t loose_bake[30]; /* счётчик «запечь пусто» (2 = обе страницы) */
static uint8_t loose_rest[30]; /* счётчик «перерисовать ПОКОЙ» после тряски (2 = обе стр.) */
/* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */ /* Плита-ПОТОЛОК: состояние/сигналы — см. объявления перед get_tile. */
@@ -1115,7 +1114,7 @@ static void do_knock(int tile_row)
/* check_knock (seg003:0706): по флагу knock (ставит play_seq на seqtbl- /* check_knock (seg003:0706): по флагу knock (ставит play_seq на seqtbl-
* командах KNOCK_UP/DOWN) тряхнуть ряд. KNOCK_DOWN (knock=-1, приземление) → * командах KNOCK_UP/DOWN) тряхнуть ряд. KNOCK_DOWN (knock=-1, приземление) →
* ряд curr_row; KNOCK_UP (knock=+1) → ряд curr_row-1. */ * ряд curr_row; KNOCK_UP (knock=+1) → ряд curr_row-1. */
void pop_check_knock(void) void pop_check_knock(void) __banked
{ {
if (knock) { if (knock) {
do_knock(Kid.curr_row - (knock > 0)); do_knock(Kid.curr_row - (knock > 0));
@@ -1126,23 +1125,18 @@ void pop_check_knock(void)
/* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest /* Сброс состояния loose (при смене комнаты): pop_loose_modif/bake/rest
* индексируются позицией тайла и не должны утечь из старой комнаты в новую. * индексируются позицией тайла и не должны утечь из старой комнаты в новую.
* Плюс останавливает падающий кусок (pop_bg). */ * Плюс останавливает падающий кусок (pop_bg). */
void pop_loose_reset(void) void pop_loose_reset(void) __banked
{ {
int i; int i;
for (i = 0; i < 30; i++) { for (i = 0; i < 30; i++) {
pop_loose_modif[i] = 0; pop_loose_modif[i] = 0;
loose_bake[i] = 0;
loose_rest[i] = 0;
} }
for (i = 0; i < 10; i++) { for (i = 0; i < 10; i++) {
pop_ceil_modif[i] = 0; pop_ceil_modif[i] = 0;
ceil_rest[i] = 0;
ceil_bake[i] = 0;
} }
pop_ceil_fell = 0; pop_ceil_fell = 0;
pop_debris_at = 0; pop_debris_at = 0;
pop_loose_landed = 0; pop_loose_landed = 0;
land_bake = 0;
pop_loose_mob_reset(); pop_loose_mob_reset();
} }
@@ -1159,18 +1153,18 @@ static void fell_on_your_head(void)
uint8_t frame = Kid.frame, action = Kid.action; uint8_t frame = Kid.frame, action = Kid.action;
if ((frame < 5 || frame >= 15) && if ((frame < 5 || frame >= 15) &&
(action < ACT_HANGCLIMB || action == ACT_TURN)) { (action < ACT_HANGCLIMB || action == ACT_TURN)) {
Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
pop_kid_hurt = 1; /* hitp_delta<0 → draw_hurt_splash */ pop_kid_hurt = 1; /* hitp_delta<0 → draw_hurt_splash */
if (take_hp(1)) { if (take_hp(1)) {
kid_set_seq(SEQ_22_CRUSHED); kid_set_seq(SEQ_22_CRUSHED);
play_seq(); pop_kid_play();
determine_col(); determine_col();
pop_kid_dead = 1; pop_kid_dead = 1;
} else if (frame != FRAME_109_CROUCH) { } else if (frame != FRAME_109_CROUCH) {
if (get_tile_behind_char() == TILE_EMPTY) if (get_tile_behind_char() == TILE_EMPTY)
Kid.x = (uint8_t)char_dx_forward(-2); Kid.x = (uint8_t)char_dx_forward(-2);
kid_set_seq(SEQ_52_LOOSE_FELL_ON_KID); kid_set_seq(SEQ_52_LOOSE_FELL_ON_KID);
play_seq(); pop_kid_play();
determine_col(); determine_col();
} }
} }
@@ -1190,7 +1184,7 @@ static void check_loose_fall_on_kid(void)
/* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ /* animate_loose (seg007:0CBA) + перерисовка изменившихся тайлов на ТЕКУЩЕЙ
* (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */ * (back) странице. Звать из основного цикла ПОСЛЕ set_draw_page(back). */
void pop_loose_tick(void) void pop_loose_tick(void) __banked
{ {
int pos; int pos;
/* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80 /* row/col ведём бегущими счётчиками, а НЕ через pos/10 и pos%10: у Z80
@@ -1201,24 +1195,17 @@ void pop_loose_tick(void)
* Инкремент+сравнение считают row/col без деления. */ * Инкремент+сравнение считают row/col без деления. */
uint8_t row = 0, col = 0; uint8_t row = 0, col = 0;
for (pos = 0; pos < 30; pos++) { for (pos = 0; pos < 30; pos++) {
if (loose_bake[pos]) { /* упал: запечь пусто (2 кадра=2 стр.) */ if (pop_loose_modif[pos] != 0) {
pop_loose_bake_empty(row, col);
loose_bake[pos]--;
} else if (loose_rest[pos]) { /* тряска кончилась: перерисовать ПОКОЙ */
/* modif уже 0 → shake_draw рисует базовый кадр (покой) обоих тайлов
* (col и col+1: правая грань loose живёт в 2,7). 2 кадра = обе
* страницы дабл-буфера — иначе на одной остаётся застывший
* дрожащий кадр правой грани → мерцание («бесконечная тряска»). */
pop_loose_shake_draw(row, col);
loose_rest[pos]--;
} else if (pop_loose_modif[pos] != 0) {
uint8_t m = ++pop_loose_modif[pos]; uint8_t m = ++pop_loose_modif[pos];
if (m & 0x80) { /* тряска (do_knock) */ if (m & 0x80) { /* тряска (do_knock) */
if (m >= 0x84) { /* конец тряски: сброс + перерисовать покой */ if (m >= 0x84) { /* конец тряски: сброс + покой */
pop_loose_modif[pos] = 0; pop_loose_modif[pos] = 0;
loose_rest[pos] = 2; /* покой на ОБЕИХ страницах */ /* modif уже 0 → кадр покоя обоих тайлов (col и col+1:
* правая грань loose живёт в соседе). ОБЕ страницы —
* иначе на одной застывает дрожащий кадр → мерцание. */
pop_set_redraw(pos, POP_RD_LOOSE, 2);
} else { } else {
pop_loose_shake_draw(row, col); /* дрожащий кадр */ pop_set_redraw(pos, POP_RD_LOOSE, 1); /* дрожащий кадр */
} }
} else if (m >= 11) { /* loose_floor_delay — падение */ } else if (m >= 11) { /* loose_floor_delay — падение */
/* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!). /* remove_loose (seg007:0EB8): тайл → EMPTY (не debris!).
@@ -1228,12 +1215,11 @@ void pop_loose_tick(void)
* (стена-вниз от тайла снизу + чёрный фон). */ * (стена-вниз от тайла снизу + чёрный фон). */
g_fg[pos] = TILE_EMPTY; g_fg[pos] = TILE_EMPTY;
pop_loose_modif[pos] = 0; pop_loose_modif[pos] = 0;
pop_loose_bake_empty(row, col); /* запечь пусто на ТЕКУЩЕЙ странице сразу */ pop_set_redraw(pos, POP_RD_LOOSE_GONE, 2); /* запечь пусто, обе стр. */
loose_bake[pos] = 1; /* + вторая страница следующим кадром */ pop_loose_mob_spawn(row, col); /* отрыв падающего куска */
pop_loose_mob_spawn(row, col); /* отрыв падающего куска (L4) */ pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению */
pop_loose_fell = (uint8_t)(pos + 1); /* сигнал приложению: кусок улетел */
} else { /* отсчёт 1..10 — дрожащий кадр */ } else { /* отсчёт 1..10 — дрожащий кадр */
pop_loose_shake_draw(row, col); pop_set_redraw(pos, POP_RD_LOOSE, 1);
} }
} }
if (++col == 10) { col = 0; row++; } /* row/col без деления */ if (++col == 10) { col = 0; row++; } /* row/col без деления */
@@ -1242,27 +1228,21 @@ void pop_loose_tick(void)
* рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в * рисуются в полосе у потолка, а провалившаяся плита становится EMPTY в
* НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */ * НАШЕЙ копии ряда (g_above) — сверху открывается колодец. */
for (col = 0; col < 10; col++) { for (col = 0; col < 10; col++) {
if (ceil_rest[col]) { if (pop_ceil_modif[col] != 0) {
pop_ceil_shake_draw(col); /* modif уже 0 → кадр покоя */
ceil_rest[col]--;
} else if (pop_ceil_modif[col] != 0) {
uint8_t m = ++pop_ceil_modif[col]; uint8_t m = ++pop_ceil_modif[col];
if (m & 0x80) { /* тряска от сотрясения (do_knock) */ if (m & 0x80) { /* тряска от сотрясения (do_knock) */
if (m >= 0x84) { pop_ceil_modif[col] = 0; ceil_rest[col] = 2; } if (m >= 0x84) { pop_ceil_modif[col] = 0;
else pop_ceil_shake_draw(col); pop_set_redraw_above(col, POP_RDA_CEIL, 2); }
else pop_set_redraw_above(col, POP_RDA_CEIL, 1);
} else if (m >= 11) { /* loose_floor_delay — плита рушится */ } else if (m >= 11) { /* loose_floor_delay — плита рушится */
pop_ceil_modif[col] = 0; pop_ceil_modif[col] = 0;
if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */ if (g_above) g_above[col] = TILE_EMPTY; /* remove_loose */
pop_ceil_bake_empty(col); /* колодец наверх на ТЕКУЩЕЙ странице */ pop_set_redraw_above(col, POP_RDA_CEIL_GONE, 2); /* колодец, обе стр. */
ceil_bake[col] = 1; /* + вторая страница следующим кадром */ pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
pop_loose_mob_spawn(-1, col); /* кусок отрывается от потолка */
pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */ pop_ceil_fell = (uint8_t)(col + 1); /* сигнал приложению */
} else { } else {
pop_ceil_shake_draw(col); /* отсчёт 1..10 — дрожащий кадр */ pop_set_redraw_above(col, POP_RDA_CEIL, 1); /* отсчёт 1..10 */
} }
} else if (ceil_bake[col]) {
pop_ceil_bake_empty(col);
ceil_bake[col]--;
} }
} }
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */ pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
@@ -1270,34 +1250,22 @@ void pop_loose_tick(void)
/* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД /* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД
* разбором нового: иначе при двух плитах, севших подряд, land_bake * разбором нового: иначе при двух плитах, севших подряд, land_bake
* затирался новым и на одной странице оставался старый пол. */ * затирался новым и на одной странице оставался старый пол. */
if (land_bake && pop_loose_landed) {
uint8_t r = 0, c = land_bake_pos;
while (c >= 10) { c -= 10; r++; }
pop_floor_bake(r, c);
land_bake = 0;
}
if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */ if (pop_loose_landed) { /* loose_land (seg007:11E8): на месте куска — щебень */
uint8_t pos = (uint8_t)(pop_loose_landed - 1); uint8_t pos = (uint8_t)(pop_loose_landed - 1);
uint8_t r = 0, c = pos; uint8_t r = 0, c = pos;
pop_loose_landed = 0; pop_loose_landed = 0;
while (c >= 10) { c -= 10; r++; } while (c >= 10) { c -= 10; r++; }
g_fg[pos] = TILE_DEBRIS; g_fg[pos] = TILE_DEBRIS;
/* Перерисовка ПО ПЛОЩАДИ ПОЛА (не bake_empty: та стирает и верх стены /* Перерисовка ПО ПЛОЩАДИ ПОЛА (не LOOSE_GONE: та стирает и верх стены
* ряда ниже — оставался чёрный прямоугольник под тайлом). */ * ряда ниже — оставался чёрный прямоугольник под тайлом). Обе
pop_floor_bake(r, c); * страницы дабл-буфера. */
land_bake_pos = pos; land_bake = 1; /* вторая страница дабл-буфера */ pop_set_redraw(pos, POP_RD_FLOOR, 2);
do_knock(r); /* удар плиты трясёт loose этого ряда */ do_knock(r); /* удар плиты трясёт loose этого ряда */
pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */ pop_debris_at = (uint8_t)(pos + 1); /* сигнал приложению (персистентность) */
} else if (land_bake) {
uint8_t r = 0, c = land_bake_pos;
while (c >= 10) { c -= 10; r++; }
pop_floor_bake(r, c);
land_bake = 0;
} }
} }
/* y_to_row_mod4 (seg006): ряд по y с оборотом mod4 (TILE_SIZEY=63). */
static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); }
/* ---- clip_char (seg006:1749), первый блок: обрезка спрайта СВЕРХУ ----- * /* ---- clip_char (seg006:1749), первый блок: обрезка спрайта СВЕРХУ ----- *
* Оригинал перед add_objtable кладёт в objtable прямоугольник клипа; блиттер * Оригинал перед add_objtable кладёт в objtable прямоугольник клипа; блиттер
@@ -1313,7 +1281,7 @@ static int8_t y_to_row(int16_t y) { return (int8_t)(((y + 60) / 63) % 4 - 1); }
* check_spike_below колонки берутся get_tile_div_mod БЕЗ 7. * check_spike_below колонки берутся get_tile_div_mod БЕЗ 7.
* *
* Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */ * Возврат: КОМНАТНЫЙ y верхней границы (0 = клипа нет). */
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked
{ {
static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 }; static const int16_t y_clip[5] = { -60, 3, 66, 129, 192 };
int wh = ((int)w + 1) >> 1; /* char_width_half */ int wh = ((int)w + 1) >> 1; /* char_width_half */
@@ -1326,7 +1294,7 @@ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h)
xr = xl + wh; /* char_x_right */ xr = xl + wh; /* char_x_right */
top_y = obj_y - (int)h + 1; /* char_top_y */ top_y = obj_y - (int)h + 1; /* char_top_y */
if (top_y >= 192) top_y = 0; /* весь спрайт ниже комнаты */ if (top_y >= 192) top_y = 0; /* весь спрайт ниже комнаты */
trow = y_to_row((int16_t)top_y); /* char_top_row */ trow = pop_y_to_row((int16_t)top_y); /* char_top_row */
cL = get_tile_div_mod(xl); if (cL < 0) cL = 0; cL = get_tile_div_mod(xl); if (cL < 0) cL = 0;
cR = get_tile_div_mod(xr); if (cR > 9) cR = 9; cR = get_tile_div_mod(xr); if (cR > 9) cR = 9;
@@ -1364,13 +1332,13 @@ static void check_leave_below(void)
Kid.action != ACT_MIDAIR && Kid.action != ACT_MIDAIR &&
(int8_t)Kid.y < 10 && (int8_t)Kid.y > -16) { (int8_t)Kid.y < 10 && (int8_t)Kid.y > -16) {
Kid.y = (uint8_t)(Kid.y + 189); Kid.y = (uint8_t)(Kid.y + 189);
Kid.curr_row = y_to_row((int16_t)Kid.y); Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
pop_leave_dir = 3; pop_leave_dir = 3;
return; return;
} }
if (Kid.y >= 211) { if (Kid.y >= 211) {
Kid.y = (uint8_t)(Kid.y - 189); Kid.y = (uint8_t)(Kid.y - 189);
Kid.curr_row = y_to_row((int16_t)Kid.y); Kid.curr_row = pop_y_to_row((int16_t)Kid.y);
pop_fell_out = 1; pop_fell_out = 1;
} }
} }
@@ -1456,13 +1424,13 @@ static int is_spike_harmful(uint8_t tilepos)
static void spiked(uint8_t tilepos) static void spiked(uint8_t tilepos)
{ {
pop_trob_modif(g_room)[tilepos] = 0xFF; pop_trob_modif(g_room)[tilepos] = 0xFF;
Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1];
Kid.x = (uint8_t)(x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + 10); Kid.x = (uint8_t)(pop_x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + 10);
Kid.x = (uint8_t)char_dx_forward(8); Kid.x = (uint8_t)char_dx_forward(8);
Kid.fall_y = 0; Kid.fall_y = 0;
take_hp(100); /* (TODO: sound_48_spiked) */ take_hp(100); /* (TODO: sound_48_spiked) */
kid_set_seq(SEQ_51_SPIKED); kid_set_seq(SEQ_51_SPIKED);
play_seq(); pop_kid_play();
determine_col(); determine_col();
pop_kid_dead = 1; pop_kid_dead = 1;
} }
@@ -1514,7 +1482,7 @@ static void check_spike_below(void)
} }
} }
void pop_phys_tick(void) void pop_phys_tick(void) __banked
{ {
if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */ if (pop_kid_dead) return; /* труп на пиках — без физики (только seq смерти) */
if (grab_timer) grab_timer--; if (grab_timer) grab_timer--;
+21 -21
View File
@@ -17,25 +17,25 @@
* col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив * col<0/>9 или row<0/>2 трактуются как стена (край уровня). Массив
* МУТАБЕЛЬНЫЙ (loose-пол при падении → empty) — передавать ту же копию, * МУТАБЕЛЬНЫЙ (loose-пол при падении → empty) — передавать ту же копию,
* что и в pop_room_draw, чтобы отрисовка видела изменения. */ * что и в pop_room_draw, чтобы отрисовка видела изменения. */
void pop_map_set(uint8_t *fg); void pop_map_set(uint8_t *fg) __banked;
/* Связи комнаты (0=нет соседа; иначе номер) + кромки швов для коллизии: /* Связи комнаты (0=нет соседа; иначе номер) + кромки швов для коллизии:
* lcol_fg[6] = col9 левого соседа (за col0) в [0..2] и его col8 (колонка −2) * lcol_fg[6] = col9 левого соседа (за col0) в [0..2] и его col8 (колонка −2)
* в [3..5]; rcol_fg[6] симметрично (col0 / col1 = колонки 10 и 11). Вторая * в [3..5]; rcol_fg[6] симметрично (col0 / col1 = колонки 10 и 11). Вторая
* колонка нужна, когда Kid стоит В шве и смотрит наружу. Звать в enter_room. */ * колонка нужна, когда Kid стоит В шве и смотрит наружу. Звать в enter_room. */
void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down, void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
const uint8_t *lcol_fg, const uint8_t *rcol_fg); const uint8_t *lcol_fg, const uint8_t *rcol_fg) __banked;
/* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob). /* Номер ТЕКУЩЕЙ комнаты (для доступа к room_modif пик/ворот в pop_trob).
* Звать в enter_room. */ * Звать в enter_room. */
void pop_map_set_room(uint8_t room); void pop_map_set_room(uint8_t room) __banked;
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP. /* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/ * pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
* респавн). */ * респавн). */
extern uint8_t pop_kid_dead; extern uint8_t pop_kid_dead;
extern uint8_t hitp_curr; extern uint8_t hitp_curr;
void pop_kid_hp_reset(void); void pop_kid_hp_reset(void) __banked;
/* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object). /* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object).
* pop_have_sword — «меч у Кида» (порт have_sword). pop_item_taken — 0 или * pop_have_sword — «меч у Кида» (порт have_sword). pop_item_taken — 0 или
@@ -46,8 +46,8 @@ extern uint8_t pop_item_taken;
/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока /* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока
* НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */ * НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */
extern uint8_t pop_flash_time; extern uint8_t pop_flash_time;
uint8_t pop_get_item_action(void); /* 0 нет / 1 присесть / 2 поднять меч */ uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч */
void pop_proc_get_object(void); /* эффект — из play_seq (опкод GET_ITEM) */ void pop_proc_get_object(void) __banked; /* эффект — из play_seq (опкод GET_ITEM) */
/* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для /* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для
* pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189). * pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189).
@@ -66,8 +66,8 @@ extern uint8_t pop_leave_timer;
* на ТЕКУЩЕЙ странице; звать в основном цикле ПОСЛЕ set_draw_page(back), * на ТЕКУЩЕЙ странице; звать в основном цикле ПОСЛЕ set_draw_page(back),
* рядом с kid-отрисовкой. */ * рядом с kid-отрисовкой. */
extern uint8_t pop_loose_modif[30]; extern uint8_t pop_loose_modif[30];
void pop_loose_tick(void); void pop_loose_tick(void) __banked;
void pop_loose_reset(void); /* сброс loose-состояния при смене комнаты */ void pop_loose_reset(void) __banked; /* сброс loose-состояния при смене комнаты */
/* Установлен в tilepos+1 в кадре, когда loose-пол упал (кусок улетел ВНИЗ). /* Установлен в tilepos+1 в кадре, когда loose-пол упал (кусок улетел ВНИЗ).
* Приложение: определить debris-приземление в комнате снизу. Снять (=0). */ * Приложение: определить debris-приземление в комнате снизу. Снять (=0). */
@@ -81,7 +81,7 @@ extern uint8_t pop_loose_fell;
* pop_loose_modif), читает pop_bg. Сигналы приложению (для персистентности * pop_loose_modif), читает pop_bg. Сигналы приложению (для персистентности
* между входами в комнату): pop_ceil_fell = col+1 провалившейся плиты, * между входами в комнату): pop_ceil_fell = col+1 провалившейся плиты,
* pop_debris_at = tilepos+1 щебня, оставшегося в ЭТОЙ комнате. Снимать (=0). */ * pop_debris_at = tilepos+1 щебня, оставшегося в ЭТОЙ комнате. Снимать (=0). */
void pop_map_set_above(uint8_t *above_row2_fg); void pop_map_set_above(uint8_t *above_row2_fg) __banked;
extern uint8_t pop_ceil_modif[10]; extern uint8_t pop_ceil_modif[10];
extern uint8_t pop_ceil_fell; extern uint8_t pop_ceil_fell;
extern uint8_t pop_debris_at; extern uint8_t pop_debris_at;
@@ -92,7 +92,7 @@ extern uint8_t pop_kid_hurt;
/* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда /* check_knock (seg003): по флагу knock (play_seq) тряхнуть loose-полы ряда
* приземления/удара. Зовётся из pop_phys_tick. */ * приземления/удара. Зовётся из pop_phys_tick. */
void pop_check_knock(void); void pop_check_knock(void) __banked;
/* Установлен в 1, когда Kid пересёк НИЖНЮЮ границу комнаты (провал в дыру /* Установлен в 1, когда Kid пересёк НИЖНЮЮ границу комнаты (провал в дыру
* или спуск-зацеп с кромки). Координаты Kid УЖЕ репроецированы в кадр * или спуск-зацеп с кромки). Координаты Kid УЖЕ репроецированы в кадр
@@ -104,14 +104,14 @@ extern uint8_t pop_fell_out;
/* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed + /* Пер-кадровая физика ПОСЛЕ kid_tick(play_seq): fall_accel + fall_speed +
* determine_col + check_action (триггер падения / do_fall / приземление). * determine_col + check_action (триггер падения / do_fall / приземление).
* Порядок как в оригинале (seg000 play_kid_frame). */ * Порядок как в оригинале (seg000 play_kid_frame). */
void pop_phys_tick(void); void pop_phys_tick(void) __banked;
/* clip_char (seg006:1749), верхняя граница клипа спрайта персонажа: если тайл /* clip_char (seg006:1749), верхняя граница клипа спрайта персонажа: если тайл
* над головой — стена/пол, всё выше линии своей полосы не рисуется (Kid, * над головой — стена/пол, всё выше линии своей полосы не рисуется (Kid,
* ушедший головой под пол при спуске/висе, не просвечивает сквозь щели). * ушедший головой под пол при спуске/висе, не просвечивает сквозь щели).
* obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер. * obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер.
* Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */ * Возврат: КОМНАТНЫЙ y границы, 0 = клипа нет. Зовёт kid_draw. */
int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h); int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h) __banked;
/* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после /* Тип препятствия/края ВПЕРЕДИ по направлению (pop_edge_type после
* pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */ * pop_edge_distance): для control (бежать / осторожный шаг / стоп у стены). */
@@ -120,26 +120,26 @@ int pop_clip_char_top(int obj_x, int obj_y, uint16_t w, uint16_t h);
#define EDGE_FLOOR 2 /* пол (нет препятствия рядом, дистанция = 11) */ #define EDGE_FLOOR 2 /* пол (нет препятствия рядом, дистанция = 11) */
/* Расстояние до края/стены впереди + побочно g_edge_type (pop_edge_type). */ /* Расстояние до края/стены впереди + побочно g_edge_type (pop_edge_type). */
int pop_edge_distance(void); int pop_edge_distance(void) __banked;
uint8_t pop_edge_type(void); uint8_t pop_edge_type(void) __banked;
/* Стена впереди в пределах стопа? Если да — прижимает Kid.x к корректной /* Стена впереди в пределах стопа? Если да — прижимает Kid.x к корректной
* позиции (передний край у стены, симметрично) и возвращает 1. Для * позиции (передний край у стены, симметрично) и возвращает 1. Для
* control_running (чистый стоп у стены). */ * control_running (чистый стоп у стены). */
int pop_wall_ahead(void); int pop_wall_ahead(void) __banked;
/* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО /* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО
* прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x. * прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x.
* check_jump_up/grab_up (K4.4). control_up гасит caller. */ * check_jump_up/grab_up (K4.4). control_up гасит caller. */
uint8_t pop_jump_up_seq(void); uint8_t pop_jump_up_seq(void) __banked;
/* ---- Вис/зацеп/спуск (K4.2/K4.3) — тайловые решения для pop_ctrl --- * /* ---- Вис/зацеп/спуск (K4.2/K4.3) — тайловые решения для pop_ctrl --- *
* Управление (control_hanging/can_climb_up/hang_fall/down_pressed) в * Управление (control_hanging/can_climb_up/hang_fall/down_pressed) в
* pop_ctrl; здесь — предикаты + выбор seq (+ выравнивание Kid.x). */ * pop_ctrl; здесь — предикаты + выбор seq (+ выравнивание Kid.x). */
uint8_t pop_hang_grab_ready(void); /* grab_timer истёк — можно climb-up */ uint8_t pop_hang_grab_ready(void) __banked; /* grab_timer истёк — можно climb-up */
uint8_t pop_hang_climb_up_seq(void); /* seq_10 / seq_73 (подтягивание) */ uint8_t pop_hang_climb_up_seq(void) __banked; /* seq_10 / seq_73 (подтягивание) */
int8_t pop_hang_wall(void); /* Shift в висе: 1=у стены,-1=fall,0=держ.*/ int8_t pop_hang_wall(void) __banked; /* Shift в висе: 1=у стены,-1=fall,0=держ.*/
uint8_t pop_hang_fall_seq(void); /* seq_23 / seq_11 (сорваться), +Kid.x */ uint8_t pop_hang_fall_seq(void) __banked; /* seq_23 / seq_11 (сорваться), +Kid.x */
uint8_t pop_down_action(void); /* 1=обработано(спуск/отступ), 0=присед */ uint8_t pop_down_action(void) __banked; /* 1=обработано(спуск/отступ), 0=присед */
#endif #endif
+78
View File
@@ -0,0 +1,78 @@
/*
* pop_redraw.c — пометки перерисовки тайлов и их разбор. См. pop_redraw.h.
*
* Живёт в W1/W2 (НЕ в резиденте W3): пометки ставит логика, которая должна
* оставаться достижимой из будущих __banked-модулей. Сам разбор зовёт
* pop_bg (резидент W3) — это законно, потому что разбор дёргает только
* главный цикл.
*/
#include <stdint.h>
#include "pop_redraw.h"
#include "pop_bg.h"
#define NTILES 30
#define NCOLS 10
static uint8_t rd_kind[NTILES], rd_cnt[NTILES];
static uint8_t rda_kind[NCOLS], rda_cnt[NCOLS];
/* Сколько всего непустых пометок: без него проход по 40 ячейкам крутился бы
* КАЖДЫЙ кадр впустую (замер 2026-07-27 на прежнем rest-цикле: ~12 % кадра). */
static uint8_t rd_pending;
void pop_redraw_reset(void)
{
uint8_t i;
for (i = 0; i < NTILES; i++) { rd_kind[i] = 0; rd_cnt[i] = 0; }
for (i = 0; i < NCOLS; i++) { rda_kind[i] = 0; rda_cnt[i] = 0; }
rd_pending = 0;
}
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages)
{
if (tilepos >= NTILES) return;
if (!rd_cnt[tilepos]) rd_pending++;
rd_kind[tilepos] = kind;
if (pages > rd_cnt[tilepos]) rd_cnt[tilepos] = pages;
}
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages)
{
if (col >= NCOLS) return;
if (!rda_cnt[col]) rd_pending++;
rda_kind[col] = kind;
if (pages > rda_cnt[col]) rda_cnt[col] = pages;
}
/* redraw_needed (seg008:0178): пройти пометки и нарисовать. row/col ведём
* бегущими счётчиками, а не через i/10 и i%10 — у Z80 нет деления, и SDCC
* выносит __divuchar/__moduchar в вершину цикла как общее подвыражение
* аргументов (30*2 делений за кадр даже когда рисовать нечего). */
void pop_redraw_needed(void)
{
uint8_t i, row = 0, col = 0, left = 0;
if (!rd_pending) return;
for (i = 0; i < NTILES; i++) {
if (rd_cnt[i]) {
switch (rd_kind[i]) {
case POP_RD_SPIKE: pop_spike_redraw(row, col); break;
case POP_RD_LOOSE: pop_loose_shake_draw(row, col); break;
case POP_RD_LOOSE_GONE: pop_loose_bake_empty(row, col); break;
case POP_RD_FLOOR: pop_floor_bake(row, col); break;
case POP_RD_LEVELDOOR: pop_leveldoor_redraw(row, col); break;
default: break;
}
if (--rd_cnt[i]) left++;
else rd_kind[i] = 0;
}
if (++col == 10) { col = 0; row++; }
}
for (i = 0; i < NCOLS; i++) {
if (rda_cnt[i]) {
if (rda_kind[i] == POP_RDA_CEIL) pop_ceil_shake_draw(i);
else pop_ceil_bake_empty(i);
if (--rda_cnt[i]) left++;
else rda_kind[i] = 0;
}
}
rd_pending = left;
}
+50
View File
@@ -0,0 +1,50 @@
/*
* pop_redraw.h — пометки «перерисовать тайл» и их разбор.
*
* Порт архитектуры оригинала: логика анимации НИЧЕГО не рисует, она ставит
* флаг (`set_redraw_full`/`set_wipe`/`redraw_20h`/`redraw_21h`, seg007), а
* отрисовка идёт отдельным проходом (`redraw_needed`, seg008:0178). У нас
* так же: pop_map/pop_trob зовут pop_set_redraw*, а pop_redraw_needed()
* (главный цикл) разбирает пометки и дёргает pop_bg.
*
* ЗАЧЕМ (кроме сходства с SDLPoP): из __banked-кода резидентная страница W3
* недостижима — ни напрямую, ни транзитивно (docs/layout_plan_v2.md §2 R2).
* Пока pop_map звал pop_bg, его нельзя было использовать из банка стражей.
* Пометки живут в W1/W2 и зовутся откуда угодно.
*
* ОТЛИЧИЕ ОТ ОРИГИНАЛА: у нас не один экран с dirty-rect, а две страницы
* дабл-буфера со своей ОЗУ-копией фона, поэтому счётчик пометки — это ЧИСЛО
* СТРАНИЦ (обычно 2), а вид перерисовки хранится явно: heal+рисовать поверх
* (транзиент) или запечь новый фон. В оригинале вид кодируется тем, в какой
* из таблиц redraw_frames_* стоит флаг.
*/
#ifndef POP_REDRAW_H
#define POP_REDRAW_H
#include <stdint.h>
/* Виды перерисовки тайла (row*10+col). */
#define POP_RD_SPIKE 1 /* пики: heal + оба тайла (redraw_21h) */
#define POP_RD_LOOSE 2 /* loose: дрожащий кадр / кадр покоя */
#define POP_RD_LOOSE_GONE 3 /* loose провалился: запечь пусто */
#define POP_RD_FLOOR 4 /* пол изменился: кнопка/щебень/поднятый меч */
#define POP_RD_LEVELDOOR 5 /* створка двери уровня (ПРАВАЯ половина) */
/* Виды перерисовки полосы у потолка (по колонке). */
#define POP_RDA_CEIL 1 /* плита-потолок: дрожащий кадр / покой */
#define POP_RDA_CEIL_GONE 2 /* плита-потолок провалилась: колодец */
/* Пометить тайл/колонку: kind — вид, pages — на скольких страницах
* дабл-буфера перерисовать (1 = только текущая, 2 = обе). Повторная
* пометка перекрывает вид и берёт максимум счётчика. */
void pop_set_redraw(uint8_t tilepos, uint8_t kind, uint8_t pages);
void pop_set_redraw_above(uint8_t col, uint8_t kind, uint8_t pages);
/* Разобрать пометки: нарисовать всё помеченное на ТЕКУЩЕЙ (back) странице и
* уменьшить счётчики. Звать из главного цикла в слое ФОНА — до kid_draw. */
void pop_redraw_needed(void);
/* Сбросить все пометки (смена комнаты/уровня). */
void pop_redraw_reset(void);
#endif
+15
View File
@@ -0,0 +1,15 @@
/*
* pop_state.c — данные, которые читают ОБЕ стороны банковой границы.
*
* Зачем отдельный модуль: sprinter-cc собирает банковый модуль с
* --dataseg BANKn, поэтому ВСЯ его писучая память (глобалы, file-static,
* статики внутри функций) линкуется В СТРАНИЦУ БАНКА и снаружи не читается
* — проверено на .map (2026-07-30: глобал банкового модуля лёг по
* 0x0001C000, а не в _DATA). Значит любую переменную, к которой нужен
* доступ и из банка, и снаружи, надо держать в НЕбанковом модуле — здесь.
*
* Модуль обязан оставаться в W1/W2: его читают и главный цикл, и банки.
*/
#include "pop_state.h"
uint8_t pop_loose_landed;
+17
View File
@@ -0,0 +1,17 @@
/*
* pop_state.h — состояние, общее для банков и W1/W2. См. pop_state.c.
*
* Правило: если переменную читают/пишут по обе стороны банковой границы —
* её место здесь, а НЕ в банковом модуле (там она уедет в страницу банка).
*/
#ifndef POP_STATE_H
#define POP_STATE_H
#include <stdint.h>
/* Кусок loose приземлился (loose_land, seg007:11E8): 0 = нет, иначе
* tilepos+1 тайла, на который он лёг. Ставит отрисовка mob (банк),
* разбирает логика loose-полов (pop_map, W1/W2). */
extern uint8_t pop_loose_landed;
#endif
+44 -73
View File
@@ -5,7 +5,14 @@
*/ */
#include "pop_trob.h" #include "pop_trob.h"
#include "pop_level.h" #include "pop_level.h"
#include "pop_bg.h" /* pop_spike_redraw */ #include "pop_geom.h"
#include "pop_redraw.h" /* пометки перерисовки тайлов (вместо прямых вызовов) */
/* pop_bg нужен ТОЛЬКО для пламени факела и пузырька зелья: это не тайловая
* перерисовка, а покадровый оверлей поверх фона (в оригинале — свои
* add_backtable/add_foretable внутри draw_tile). Всё остальное идёт через
* пометки, поэтому pop_process_trobs — единственная функция модуля, которую
* нельзя звать из __banked-кода. */
#include "pop_bg.h"
/* Коды тайлов (fg & 0x1F), которые анимируются. */ /* Коды тайлов (fg & 0x1F), которые анимируются. */
#define TILE_SPIKE 0x02 #define TILE_SPIKE 0x02
@@ -30,11 +37,6 @@ static trob_t trobs[TROBS_MAX];
static uint8_t trobs_count; static uint8_t trobs_count;
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */ static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */
/* Отложенная перерисовка «покоя» законченных пик/кнопок текущей комнаты
* (2 кадра = обе страницы дабл-буфера, как loose_rest). */
static uint8_t spike_rest[POP_ROOMTILES];
static uint8_t button_rest[POP_ROOMTILES];
/* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button: /* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её * признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
* изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под * изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под
@@ -43,36 +45,19 @@ static uint8_t button_rest[POP_ROOMTILES];
* расхождение было именно в безусловном редрое). */ * расхождение было именно в безусловном редрое). */
static uint8_t trob_drawn[POP_ROOMTILES]; static uint8_t trob_drawn[POP_ROOMTILES];
/* Сколько тайлов ждут отложенного редроя (spike_rest/button_rest). Без
* него цикл по 30 тайлам крутился КАЖДЫЙ кадр впустую: замер в MAME
* (2026-07-27) — служебная часть pop_process_trobs ~50 000 тактов при
* кадре 430 080, то есть ~12 % кадра на пустой проход. */
static uint8_t rest_pending;
/* Дверь уровня: отложенный редрой ВТОРОЙ страницы дабл-буфера. Створка
* анимируется покадрово в back-страницу, поэтому последний кадр анимации
* попадает только на одну из двух страниц — вторая застревает на шаг раньше
* и дверь «дрожит» через кадр. ldoor_pos = tilepos ПРАВОЙ половины. */
static uint8_t ldoor_rest, ldoor_pos;
/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и /* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
* выбора следующего кадра пламени. */ * выбора следующего кадра пламени. */
static unsigned long trob_seed; static unsigned long trob_seed;
static uint16_t trob_prandom(uint16_t maxv) #define trob_prandom(maxv) pop_prandom(&trob_seed, (maxv))
{
trob_seed = trob_seed * 214013UL + 2531011UL;
return (uint16_t)((uint16_t)(trob_seed >> 16) % (uint16_t)(maxv + 1));
}
void pop_trob_reset(void) void pop_trob_reset(void)
{ {
uint8_t i; uint8_t i;
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0; for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; } for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
rest_pending = 0; pop_redraw_reset();
ldoor_rest = 0;
trobs_count = 0; trobs_count = 0;
} }
@@ -116,9 +101,8 @@ void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
void pop_trob_room_changed(void) void pop_trob_room_changed(void)
{ {
uint8_t i; uint8_t i;
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; } for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
rest_pending = 0; pop_redraw_reset();
ldoor_rest = 0;
} }
/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её /* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
@@ -361,38 +345,35 @@ uint8_t pop_trob_room_active(uint8_t room)
return 0; return 0;
} }
static uint8_t torch_tick; /* делитель скорости анимации пламени */
/* Темп анимации пламени в ЛОГИЧЕСКИХ кадрах (один логический кадр = 3
* vsync и соответствует игровому тику оригинала):
* 1 — кадр пламени каждый тик, как в оригинале; кадры при этом ровные по
* тактам, что нужно для счёта бюджета;
* 2 — вдвое реже (так было до 2026-07-30; пламя шло на половинной
* скорости, а бюджет кадра «прыгал» на 6.2 % от того, попал кадр на
* анимацию или нет). */
#define TORCH_ANIM_DIV 1
#if TORCH_ANIM_DIV > 1
static uint8_t torch_tick;
#define TORCH_ANIM_NOW ((uint8_t)(torch_tick % TORCH_ANIM_DIV) == 0)
#else
#define TORCH_ANIM_NOW 1
#endif
void pop_process_trobs(uint8_t cur_room) void pop_process_trobs(uint8_t cur_room)
{ {
uint8_t i, w = 0; uint8_t i, w = 0;
#if TORCH_ANIM_DIV > 1
torch_tick++; torch_tick++;
#endif
/* Перерисовка «покоя» законченных пик/кнопок (обе страницы). /* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
* row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC * row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее * выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет * подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */ * активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
if (rest_pending) {
uint8_t row = 0, col = 0, left = 0;
for (i = 0; i < POP_ROOMTILES; i++) {
if (spike_rest[i]) {
pop_spike_redraw(row, col);
if (--spike_rest[i]) left++;
}
if (button_rest[i]) {
pop_button_redraw(row, col);
if (--button_rest[i]) left++;
}
if (++col == 10) { col = 0; row++; }
}
rest_pending = left;
}
if (ldoor_rest) { /* доложить финальный кадр створки на 2-ю страницу */
pop_leveldoor_redraw(ldoor_pos / 10, ldoor_pos % 10);
ldoor_rest--;
}
if (trobs_count == 0) return; if (trobs_count == 0) return;
/* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не /* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
@@ -430,11 +411,9 @@ void pop_process_trobs(uint8_t cur_room)
break; break;
case TILE_TORCH: case TILE_TORCH:
case TILE_TORCH_DEBRIS: case TILE_TORCH_DEBRIS:
/* animate_torch (seg007:03C1): кадр пламени; анимация вечная — /* animate_torch (seg007:03C1): анимация вечная — type остаётся
* type остаётся 1, пока комната отрисована. Смена кадра — через * 1, пока комната отрисована. Темп — TORCH_ANIM_DIV. */
* ЛОГИЧЕСКИЙ кадр (torch_tick): у нас кадр короче игрового тика if (room == cur_room && TORCH_ANIM_NOW)
* оригинала, и пламя «кипело» вдвое быстрее нужного. */
if (room == cur_room && (torch_tick & 1) == 0)
mod[tp] = get_torch_frame(mod[tp]); mod[tp] = get_torch_frame(mod[tp]);
break; break;
case TILE_POTION: case TILE_POTION:
@@ -477,24 +456,20 @@ void pop_process_trobs(uint8_t cur_room)
* неизменной сигнатурой, и стёртое уже не возвращается: рядом с * неизменной сигнатурой, и стёртое уже не возвращается: рядом с
* Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной * Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной
* странице дабл-буфера из двух). */ * странице дабл-буфера из двух). */
uint8_t trow = tp / 10, tcol = tp % 10; /* Последний кадр (пика убралась, trob умирает) — пометить ОБЕ
pop_spike_redraw(trow, tcol); * страницы: живой trob метит каждый кадр по одной. */
/* Последний кадр (пика убралась, trob умирает) — на ВТОРУЮ страницу pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1));
* его уже никто не положит: ставим отложенный редрой. */
if (type < 0) { spike_rest[tp] = 1; rest_pending++; }
} }
if (room == cur_room && code == TILE_LEVELDOOR) { if (room == cur_room && code == TILE_LEVELDOOR) {
/* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там /* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
* её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе * её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
* kid_heal вернул бы из фона закрытую створку. Куски двери * kid_heal вернул бы из фона закрытую створку. Куски двери
* непрозрачны, поэтому wipe не нужен. */ * непрозрачны, поэтому wipe не нужен. */
uint8_t trow = tp / 10, tcol = tp % 10; /* Створку рисует ПРАВАЯ половина двери; последний кадр анимации
if (tcol + 1 < 10) { * помечаем на обе страницы, иначе дверь дрожит через кадр. */
pop_leveldoor_redraw(trow, tcol + 1); if (tp % 10 + 1 < 10)
/* Анимация кончилась — этот кадр лёг только на ОДНУ страницу; pop_set_redraw((uint8_t)(tp + 1), POP_RD_LEVELDOOR,
* повторить его на второй, иначе дверь дрожит через кадр. */ (uint8_t)(type < 0 ? 2 : 1));
if (type < 0) { ldoor_pos = (uint8_t)(tp + 1); ldoor_rest = 1; }
}
} }
if (room == cur_room && code == TILE_SWORD) { if (room == cur_room && code == TILE_SWORD) {
/* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1): /* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1):
@@ -503,9 +478,7 @@ void pop_process_trobs(uint8_t cur_room)
* следующим кадром через rest-цикл. */ * следующим кадром через rest-цикл. */
uint8_t sig = (uint8_t)(mod[tp] == 1); uint8_t sig = (uint8_t)(mod[tp] == 1);
if (sig != trob_drawn[tp]) { if (sig != trob_drawn[tp]) {
uint8_t trow = tp / 10, tcol = tp % 10; pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */
pop_button_redraw(trow, tcol);
button_rest[tp] = 1; rest_pending++;
trob_drawn[tp] = sig; trob_drawn[tp] = sig;
} }
} }
@@ -517,9 +490,7 @@ void pop_process_trobs(uint8_t cur_room)
* считаем только в момент редроя (редко), а не каждый кадр. */ * считаем только в момент редроя (редко), а не каждый кадр. */
uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1); uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
if (sig != trob_drawn[tp]) { if (sig != trob_drawn[tp]) {
uint8_t trow = tp / 10, tcol = tp % 10; pop_set_redraw(tp, POP_RD_FLOOR, 2); /* обе страницы */
pop_button_redraw(trow, tcol);
button_rest[tp] = 1; rest_pending++;
trob_drawn[tp] = sig; trob_drawn[tp] = sig;
} }
} }
+68 -15
View File
@@ -15,9 +15,43 @@
#include "pop_map.h" #include "pop_map.h"
#include "pop_level.h" #include "pop_level.h"
#include "pop_trob.h" #include "pop_trob.h"
#include "pop_redraw.h"
#include "pop_guard.h"
#include "pop_cheat.h"
__sfr __at (0xFE) io_border; __sfr __at (0xFE) io_border;
/* ПРОФИЛИРОВАНИЕ ПОЛОСАМИ БОРДЮРА (включено по умолчанию; make PROF=0 —
* выключить). Цвет бордюра держится, пока фаза считает, поэтому высота
* полосы на скриншоте = время фазы в растровых строках. Это единственный
* способ смотреть профиль без дебаггера: выборка PC вырождается (мост MAME
* отвечает из фреймового колбэка, все сэмплы падают в обработчик
* прерывания); точные такты — breakpoint'ы с {printf totalcycles; g}.
*
* ЦВЕТА: у бордюра Sprinter бит 0 (красный) ИГНОРИРУЕТСЯ, работают только
* бит 1 (синий) и бит 2 (зелёный) — значит различимых состояний ровно
* ЧЕТЫРЕ, и значения обязаны быть чётными. Отсюда фазы сгруппированы по
* четыре, а не по семь (нечётные номера раньше сливались с соседями и
* полосы не читались):
* 0 чёрный — ожидание vsync (запас кадра)
* 2 синий — ввод, heal и ЛОГИКА (kid_tick/физика/страж)
* 4 зелёный — слой ФОНА (loose/trobs/redraw_needed/шов)
* 6 циан — СПРАЙТЫ (страж, Kid, fore поверх них) */
#ifndef PROF_BORDER
#define PROF_BORDER 1
#endif
#if PROF_BORDER
#define PROF(n) io_border = (n)
#else
#define PROF(n) ((void)0)
#endif
/* Сколько банков грузит crt0_banked (читает его стартовый код). */
const uint8_t n_banks = 3;
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
uint8_t pop_cheats;
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */ /* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
#define SEQ_STAND 2 #define SEQ_STAND 2
#define ROOM_1 1 #define ROOM_1 1
@@ -160,6 +194,7 @@ static void enter_room(uint8_t room)
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */ pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */ pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */ pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ /* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним, * per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
* а не по свежему backtable. Фаза P0. */ * а не по свежему backtable. Фаза P0. */
@@ -197,17 +232,25 @@ int main(void)
#ifdef ROOMNAV #ifdef ROOMNAV
uint8_t nav_prev = 0; uint8_t nav_prev = 0;
#endif #endif
uint8_t cheat_prev = 0;
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */ if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
puts("pop_bg_load failed"); puts("pop_bg_load failed");
return 1; return 1;
} }
pop_cheats = 1; /* режим разработки: читы включены */
pop_guard_reset();
if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
printf("kid_data.bin load failed\n");
return 1;
}
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */ pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */ if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
puts("kid atlas load failed"); puts("kid atlas load failed");
return 1; return 1;
} }
if (pop_level_load("res2001.bin") != 0 && /* сырой уровень 1 в EMM-страницу */ if (pop_level_load("LEVELS\\res2001.bin") != 0 && /* сырой уровень 1 в EMM-страницу */
pop_level_load("a:\\res2001.bin") != 0) { pop_level_load("a:\\res2001.bin") != 0) {
puts("level load failed"); puts("level load failed");
return 1; return 1;
@@ -215,7 +258,7 @@ int main(void)
initgraph(); initgraph();
gfx_set_visible_page(0); gfx_set_visible_page(0);
if (gfx_pal_fload(0, "kid.pal") < 0) /* единая игровая палитра */ if (gfx_pal_fload(0, "KID\\kid.pal") < 0) /* единая игровая палитра */
gfx_pal_fload(0, "a:\\kid.pal"); gfx_pal_fload(0, "a:\\kid.pal");
gfx_pal_sync(); /* палитра 0 -> плоскость 1 */ gfx_pal_sync(); /* палитра 0 -> плоскость 1 */
@@ -249,7 +292,7 @@ int main(void)
/* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один /* SPACE — тумблер дабл-буфера (edge, чтоб одно нажатие = один
* переключ). Выключение возвращает показ на страницу 0. */ * переключ). Выключение возвращает показ на страницу 0. */
io_border = 2; /* синий - начало */ PROF(2); /* ввод + heal */
uint8_t sp = kbd_raw_down(KBD_SPACE); uint8_t sp = kbd_raw_down(KBD_SPACE);
if (sp && !sp_prev) { if (sp && !sp_prev) {
@@ -266,8 +309,14 @@ int main(void)
* стоит, можно спокойно разбирать позу Kid и окклюзию. */ * стоит, можно спокойно разбирать позу Kid и окклюзию. */
if (kbd_raw_down(0x16)) frozen = 1; if (kbd_raw_down(0x16)) frozen = 1;
if (kbd_raw_down(0x1E)) frozen = 0; if (kbd_raw_down(0x1E)) frozen = 0;
{ /* Читы (seg000: switch под if (cheats_enabled)). K — убить
* стража; по фронту нажатия, чтобы не срабатывало каждый кадр. */
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
cheat_prev = k;
}
if (frozen) { if (frozen) {
io_border = 0; /* черный: ждём vsync */ PROF(0); /* чёрный: ждём vsync */
gfx_wait_vsync(); gfx_wait_vsync();
continue; continue;
} }
@@ -309,9 +358,12 @@ int main(void)
pop_ctrl_tick(); /* ввод -> control(): смена seq */ pop_ctrl_tick(); /* ввод -> control(): смена seq */
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */ kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
pop_guard_heal(); /* и прошлый кадр стража */
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */ pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
PROF(2); /* логика (тот же синий) */
kid_tick(); /* play_seq: следующий кадр */ kid_tick(); /* play_seq: следующий кадр */
pop_phys_tick(); /* K3: падение/приземление/стена */ pop_phys_tick(); /* K3: падение/приземление/стена */
pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */
if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */ if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */
uint8_t down = pop_room_link(cur_room, LINK_DOWN); uint8_t down = pop_room_link(cur_room, LINK_DOWN);
pop_fell_out = 0; pop_fell_out = 0;
@@ -334,14 +386,14 @@ int main(void)
} }
} }
io_border = 4; /* зеленый */ PROF(4); /* слой фона */
pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */ pop_loose_tick(); /* loose-полы: анимация + перерисовка (СЛОЙ ФОНА */
/* — ДО Kid, чтобы Kid всегда был поверх плиты) */ /* — ДО Kid, чтобы Kid всегда был поверх плиты) */
io_border = 6; /* голубой */ pop_process_trobs(cur_room); /* пики/ворота/дверь: продвинуть СОСТОЯНИЕ (порт */
pop_process_trobs(cur_room); /* пики/ворота: продвинуть + перерисовать (тоже */ /* process_trobs — рисования внутри нет) */
/* слой фона — ДО Kid) */ pop_redraw_needed(); /* и разобрать пометки перерисовки тайлов (порт */
io_border = 4; /* синий */ /* redraw_needed, seg008:0178) — СЛОЙ ФОНА, до Kid */
{ /* ворота соседа слева (room8) видны в col0 — */ { /* ворота соседа слева (room8) видны в col0 — */
uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */ uint8_t lft = pop_room_link(cur_room, 0); /* редрой шва ТОЛЬКО при изменении openness */
if (lft) { if (lft) {
@@ -358,7 +410,6 @@ int main(void)
} }
} }
} }
io_border = 2; /* зеленый */
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */ if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
dead_frames++; dead_frames++;
if (dead_frames > RESPAWN_SETTLE && if (dead_frames > RESPAWN_SETTLE &&
@@ -371,13 +422,11 @@ int main(void)
pop_kid_hp_reset(); pop_kid_hp_reset();
} }
} }
io_border = 6; /* голубой */
if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */ if (pop_loose_fell) { /* упавший кусок → debris в комнате снизу */
uint8_t fpos = (uint8_t)(pop_loose_fell - 1); uint8_t fpos = (uint8_t)(pop_loose_fell - 1);
uint8_t fcol = (uint8_t)(fpos % 10); uint8_t fcol = (uint8_t)(fpos % 10);
uint8_t down = pop_room_link(cur_room, LINK_DOWN); uint8_t down = pop_room_link(cur_room, LINK_DOWN);
pop_loose_fell = 0; pop_loose_fell = 0;
io_border = 2; /* синий */
add_tile_override(cur_room, fpos, TILE_EMPTY); /* сама плита исчезла */ add_tile_override(cur_room, fpos, TILE_EMPTY); /* сама плита исчезла */
if (down) { if (down) {
int8_t lrow = pop_room_col_landing(down, fcol); int8_t lrow = pop_room_col_landing(down, fcol);
@@ -421,15 +470,17 @@ int main(void)
flash_bg(255, 85, 85); /* color_12_brightred */ flash_bg(255, 85, 85); /* color_12_brightred */
flash_on = 1; flash_on = 1;
} }
io_border = 4; /* зеленый */
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */ update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
PROF(6); /* спрайты: страж, Kid, fore поверх */
pop_guard_draw(); /* страж — ДО Kid (порядок как в оригинале) */
PROF(6); /* kid_draw (тот же циан) */
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */ kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */ pop_loose_mob_draw_over(); /* падающая плита НИЖЕ Kid → поверх него (y-порядок) */
if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */ if (pop_kid_hurt) { /* HP убавилось → «брызги» удара (draw_hurt_splash) */
kid_draw_splash(); kid_draw_splash();
pop_kid_hurt = 0; pop_kid_hurt = 0;
} }
io_border = 0; /* черный */ PROF(6); /* fore поверх Kid + борта */
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(), pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
kid_fp_width(), kid_fp_height(), kid_fp_width(), kid_fp_height(),
Kid.direction, Kid.frame, Kid.action); Kid.direction, Kid.frame, Kid.action);
@@ -439,7 +490,7 @@ int main(void)
* сделан (скрытая страница), wait_vsync ловит начало кадра, * сделан (скрытая страница), wait_vsync ловит начало кадра,
* set_visible_page переключает во время бланка (gfx.h паттерн). */ * set_visible_page переключает во время бланка (gfx.h паттерн). */
io_border = 0; /* черный: ждём vsync */ PROF(0); /* чёрный: ждём vsync */
/* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО /* Отладка: номер отрисованной комнаты в верхнем борте — рисуем ТОЛЬКО
* при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера), * при её смене (change-driven, счётчик 2 = обе страницы дабл-буфера),
@@ -484,6 +535,8 @@ int main(void)
closegraph(); closegraph();
pop_bg_free(); pop_bg_free();
pop_kid_free(); pop_kid_free();
pop_guard_free();
pop_kid_data_free();
pop_level_free(); pop_level_free();
return 0; return 0;
} }
@@ -15,6 +15,8 @@ import os
import re import re
SRC = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/src" SRC = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/src"
OUT_BIN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "poc", "res", "kid")
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "roomtest", "kid_data.h") "..", "roomtest", "kid_data.h")
SEQTBL_BASE = 0x196E SEQTBL_BASE = 0x196E
@@ -24,10 +26,11 @@ def read(name):
return open(os.path.join(SRC, name)).read() return open(os.path.join(SRC, name)).read()
def parse_frame_table(txt): def parse_frame_table(txt, name="frame_table_kid"):
"""frame_table_kid[] -> список (image, dx, dy, flags). Записи вида """frame_table_kid[]/frame_tbl_guard[] -> список (image, dx, dy, flags).
{ IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как OR-выражения.""" Записи вида { IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как
m = re.search(r'const frame_type frame_table_kid\[\]\s*=\s*\{(.*?)\n\};', txt, re.S) OR-выражения."""
m = re.search(r'const frame_type ' + name + r'\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
body = m.group(1) body = m.group(1)
frames = [] frames = []
for row in re.findall(r'\{([^}]*)\}', body): for row in re.findall(r'\{([^}]*)\}', body):
@@ -63,6 +66,7 @@ def main():
seqc = read("seqtbl.c") seqc = read("seqtbl.c")
frames = parse_frame_table(seg006) frames = parse_frame_table(seg006)
gframes = parse_frame_table(seg006, "frame_tbl_guard")
seqtbl = parse_byte_array(seqc, "original_seqtbl") seqtbl = parse_byte_array(seqc, "original_seqtbl")
offs = parse_word_array(seqc, "original_seqtbl_offsets") offs = parse_word_array(seqc, "original_seqtbl_offsets")
@@ -74,22 +78,25 @@ def main():
f.write("#ifndef KID_DATA_H\n#define KID_DATA_H\n#include <stdint.h>\n\n") f.write("#ifndef KID_DATA_H\n#define KID_DATA_H\n#include <stdint.h>\n\n")
f.write("#define SEQTBL_BASE 0x%04Xu\n\n" % SEQTBL_BASE) f.write("#define SEQTBL_BASE 0x%04Xu\n\n" % SEQTBL_BASE)
# frame table # frame table и seqtbl В .h НЕ ПОПАДАЮТ — они уезжают в kid_data.bin
f.write("/* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid). */\n") # (EMM-страница): 3.5 КБ в _CODE окна W1/W2 нам дороже, чем один
# gfx_w0_map за тик. См. applications/PoP/docs/layout_plan_v2.md §5.1.
f.write("/* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid).\n"
" * САМА таблица кадров и seqtbl лежат в kid_data.bin (EMM-\n"
" * страница); здесь только тип и размеры. */\n")
f.write("typedef struct { uint8_t image; int8_t dx, dy; " f.write("typedef struct { uint8_t image; int8_t dx, dy; "
"uint8_t flags, sword; } kframe;\n") "uint8_t flags, sword; } kframe;\n")
f.write("#define KID_NFRAMES %d\n" % len(frames)) f.write("#define KID_NFRAMES %d\n" % len(frames))
f.write("static const kframe kid_frames[KID_NFRAMES] = {\n") f.write("#define KID_SEQTBL_LEN %d\n\n" % len(seqtbl))
for (img, dx, dy, fl, sw) in frames:
dxs = dx - 256 if dx > 127 else dx
dys = dy - 256 if dy > 127 else dy
f.write(" {%3d,%4d,%4d,0x%02X,0x%02X},\n" % (img, dxs, dys, fl, sw))
f.write("};\n\n")
# seqtbl bytes # раскладка kid_data.bin (смещения ОТ НАЧАЛА ДАННЫХ страницы)
f.write("#define KID_SEQTBL_LEN %d\n" % len(seqtbl)) f.write("/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */\n")
f.write("static const uint8_t kid_seqtbl[KID_SEQTBL_LEN] = {\n") f.write("#define KID_BIN_FRAMES_OFF 0\n")
f.write(emit_c(seqtbl) + "\n};\n\n") f.write("#define KID_BIN_SEQTBL_OFF %d\n" % (len(frames) * 5))
f.write("/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс\n"
" * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */\n")
f.write("#define KID_NGFRAMES %d\n" % len(gframes))
f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl)))
# seq offsets # seq offsets
f.write("#define KID_NSEQ %d\n" % len(offs)) f.write("#define KID_NSEQ %d\n" % len(offs))
@@ -97,6 +104,17 @@ def main():
f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n") f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n")
f.write("#endif\n") f.write("#endif\n")
# kid_data.bin: кадры (по 5 байт) + seqtbl подряд
os.makedirs(OUT_BIN_DIR, exist_ok=True)
binpath = os.path.join(OUT_BIN_DIR, "kid_data.bin")
with open(binpath, "wb") as b:
for (img, dx, dy, fl, sw) in frames:
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
b.write(bytes(seqtbl))
for (img, dx, dy, fl, sw) in gframes:
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
print(f"kid_data.bin: {len(frames)*5 + len(seqtbl) + len(gframes)*5} Б (кадры Kid + seqtbl + кадры стража) -> {binpath}")
print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, " print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, "
f"{len(offs)} seq-оффсетов -> {OUT}") f"{len(offs)} seq-оффсетов -> {OUT}")
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""
pop_pack_guard.py — упаковщик спрайтов СТРАЖА (chtab_5_guard, база 750).
Оригинал грузит стражей из отдельного .DAT по типу уровня
(GUARD/FAT/SKEL/VIZIER/SHADOW, seg000:1092); уровень 1 — обычный «guard»,
то есть data/GUARD/res(750+id).png. Палитра — НЕ из PNG: оригинал
подменяет её из res10.bin (guard_palettes, 7 палитр по 16 цветов, 6-бит) по
level.guards_color; на уровне 1 у обоих стражей color = 2.
Выход: poc/res/guard/g0..gN.atl (по 8 спрайтов на страницу, адресация
id>>3 / id&7 — как у Kid) + группа палитры 0x90..0x9F в общий kid.pal
(его собирает pop_pack_kid.py, см. GUARD_PAL_BASE там).
"""
import os
import sys
from PIL import Image
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import pop_pack_bg as B
from pop_pack_kid import transpose_cols
GUARD_DIR = os.path.join(HERE, "..", "SDLPoP", "data", "GUARD")
PAL_BIN = os.path.join(HERE, "..", "SDLPoP", "data", "PRINCE", "res10.bin")
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "guard")
PAL_BASE = 0x90 # слоты палитры Sprinter под стража
COLOR = 2 # level.guards_color обоих стражей уровня 1
SHIFT = 3 # g[id>>3], idx id&7 (спрайты крупные)
MASK = (1 << SHIFT) - 1
MAX_ID = 33 # атлас-индексы 0..33 = res751..res784
def load_guard(id):
"""Атлас-индекс id (== frame.image) -> res(751+id).png -> (w, h, 8bpp
COLUMN-MAJOR); 0 -> 0xFF (прозрачность).
Off-by-one как у Kid (см. pop_pack_kid.load_kid): load_chtab_from_file
зовётся с ресурсом 750, а images[i-1] = res(750+i), то есть images[0] =
res751. Рисование берёт get_image(chtab, obj_id+1 1) = images[image],
значит frame.image=N -> res(751+N). С res(750+N) страж стоял в позе
предыдущего кадра (замечено на frame_166_stand_inactive: image 17
рисовался как res767 — выпад — вместо res768). """
path = os.path.join(GUARD_DIR, f"res{751 + id}.png")
if not os.path.exists(path):
return None
im = Image.open(path)
if im.mode != "P":
B.die(f"res{750+id}: mode {im.mode}, ожидался P")
src = im.tobytes()
rowmajor = bytes((0xFF if v == 0 else PAL_BASE + v) for v in src)
return im.width, im.height, transpose_cols(im.width, im.height, rowmajor)
def guard_palette():
"""16 цветов (r,g,b 0..63) из res10.bin для COLOR."""
b = open(PAL_BIN, "rb").read()
off = 0x30 * COLOR - 0x30
return [(b[off + i * 3], b[off + i * 3 + 1], b[off + i * 3 + 2]) for i in range(16)]
def main():
os.makedirs(OUT_DIR, exist_ok=True)
pages = {}
n = 0
for id in range(0, MAX_ID + 1):
g = load_guard(id)
if g is None:
continue
pages.setdefault(id >> SHIFT, {})[id & MASK] = g
n += 1
if not pages:
B.die("нет спрайтов стража")
total = 0
for pg in range(max(pages) + 1):
nm = f"g{pg}.atl"
cnt, sz = B.pack_atlas(os.path.join(OUT_DIR, nm), pages.get(pg, {}))
total += sz
print(f" {nm}: {len(pages.get(pg, {}))} спрайтов, каталог {cnt}, {sz} Б")
print(f"ИТОГО: {n} спрайтов стража, {max(pages)+1} EMM-страниц, {total} Б -> {OUT_DIR}")
if __name__ == "__main__":
main()
@@ -25,6 +25,7 @@ KID_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDL
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid") OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
GUARD_PAL_BASE = 0x90 # chtab_5 (страж) -> слоты 0x90..0x9F
SWORD_IDS = tuple(range(20, 28)) # sword_tbl для кадров 229..236 (найти-убрать) SWORD_IDS = tuple(range(20, 28)) # sword_tbl для кадров 229..236 (найти-убрать)
SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ) SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ)
MASK = (1 << SHIFT) - 1 MASK = (1 << SHIFT) - 1
@@ -108,6 +109,14 @@ def build_palette(out_path):
for i, (r, g, bl) in enumerate(_read16(path)): for i, (r, g, bl) in enumerate(_read16(path)):
o = (base + i) * 4 o = (base + i) * 4
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0)) pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
# Палитра стража берётся НЕ из .pal, а из res10.bin (guard_palettes:
# 7 палитр по 16 цветов, 6-бит) по level.guards_color; на уровне 1 у
# обоих стражей color = 2. См. toolchain/pop_pack_guard.py.
import pop_pack_guard as G
for i, (r, g, bl) in enumerate(G.guard_palette()):
o = (GUARD_PAL_BASE + i) * 4
pal[o:o + 4] = bytes((B.scale6to8(bl), B.scale6to8(g), B.scale6to8(r), 0))
with open(out_path, "wb") as f: with open(out_path, "wb") as f:
f.write(pal) f.write(pal)
+21 -2
View File
@@ -23,6 +23,7 @@
# CODE=W1|W2 DATA=W1|W2|SAME BANKED=W1|W3 # CODE=W1|W2 DATA=W1|W2|SAME BANKED=W1|W3
# Example: --memory-manual CODE=W2,DATA=W2,BANKED=W3 # Example: --memory-manual CODE=W2,DATA=W2,BANKED=W3
# -I PATH additional include path (repeatable) # -I PATH additional include path (repeatable)
# -DNAME[=VAL] preprocessor define, passed to sdcc as is (repeatable)
# -L 0xADDR code load address (default: derived from --memory mode) # -L 0xADDR code load address (default: derived from --memory mode)
# -E 0xADDR entry address (default: same as -L) # -E 0xADDR entry address (default: same as -L)
# -S 0xADDR stack address (default: 0xBFFE) # -S 0xADDR stack address (default: 0xBFFE)
@@ -32,6 +33,9 @@
# --data-loc 0xN override SDCC --data-loc (default: derived from --memory) # --data-loc 0xN override SDCC --data-loc (default: derived from --memory)
# -Wl FLAG extra linker flag (repeatable) # -Wl FLAG extra linker flag (repeatable)
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked # --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
# --bank-data put bank modules' writable data INTO the bank page
# (default: it goes to the shared _DATA in W1/W2, which
# stays mapped and is visible from everywhere)
# automatically and adds -Wl-b_BANKN=0x{N}C000 # automatically and adds -Wl-b_BANKN=0x{N}C000
# --w3 FILE.c place FILE.c resident in window 3 (0xC000), called # --w3 FILE.c place FILE.c resident in window 3 (0xC000), called
# DIRECTLY (no trampoline). Repeatable. Defaults to # DIRECTLY (no trampoline). Repeatable. Defaults to
@@ -106,6 +110,8 @@ LD_EXTRA=()
MKEXE_EXTRA=() MKEXE_EXTRA=()
BANK_SPECS=() # entries like "1=engine.c" BANK_SPECS=() # entries like "1=engine.c"
W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3) W3_SPECS=() # entries like "mod.c" — резидентные модули окна W3 (--w3)
USER_DEFS=() # -DFOO / -DFOO=bar — пробрасываются в sdcc как есть
BANK_LOCAL_DATA=0 # 1 (--bank-data): писучие данные банков — В СТРАНИЦУ банка
W3_RELS=() # заполняется при компиляции W3-модулей W3_RELS=() # заполняется при компиляции W3-модулей
W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3 W3_LD_FLAGS=() # -Wl-b_W3CODE=0xC000, если есть --w3
USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small) USER_SET_MEMORY="" # непусто, если --memory задан явно (для --w3 авто-small)
@@ -139,7 +145,9 @@ while [[ $# -gt 0 ]]; do
--w3) W3_SPECS+=("$2"); shift 2;; --w3) W3_SPECS+=("$2"); shift 2;;
--stack-size) STACK_SIZE="$2"; shift 2;; --stack-size) STACK_SIZE="$2"; shift 2;;
-Wl) LD_EXTRA+=("$2"); shift 2;; -Wl) LD_EXTRA+=("$2"); shift 2;;
-D*) USER_DEFS+=("$1"); shift;;
--bank) BANK_SPECS+=("$2"); shift 2;; --bank) BANK_SPECS+=("$2"); shift 2;;
--bank-data) BANK_LOCAL_DATA=1; shift;;
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;; --mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
--max-allocs) MAX_ALLOCS="$2"; shift 2;; --max-allocs) MAX_ALLOCS="$2"; shift 2;;
--gfx) GFX_MODE="$2"; shift 2;; --gfx) GFX_MODE="$2"; shift 2;;
@@ -366,7 +374,7 @@ run "$SDASZ80" -o "$HEAP_TOP_REL" "$HEAP_TOP_SRC"
# 2. user sources → .rel (HOME) # 2. user sources → .rel (HOME)
USER_RELS=() USER_RELS=()
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}") CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" -I "$BGI_INC_DIR" "${USER_INCS[@]}" "${USER_DEFS[@]}")
[[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT) [[ $DEBUG_RT -eq 1 ]] && CC_FLAGS+=(-DDEBUG_RT)
[[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS") [[ -n "$MAX_ALLOCS" ]] && CC_FLAGS+=(--max-allocs-per-node "$MAX_ALLOCS")
for src in "${SOURCES[@]}"; do for src in "${SOURCES[@]}"; do
@@ -420,8 +428,19 @@ if [[ "$CRT0_TYPE" == "banked" ]]; then
bank_n="${spec%%=*}" bank_n="${spec%%=*}"
bank_src="${spec#*=}" bank_src="${spec#*=}"
rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel" rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel"
# По умолчанию --dataseg НЕ задаём: писучие данные банкового
# модуля (глобалы, file-static, статики функций) остаются в
# общем _DATA, то есть в W1/W2 — они замаплены всегда и видны
# и банку, и остальному коду. С --dataseg BANKn они уезжают в
# СТРАНИЦУ БАНКА и снаружи не читаются (проверено на .map:
# глобал банкового модуля лёг по 0x0001C000): это экономит
# W1/W2, но требует ручного контроля видимости, поэтому
# включается явно через --bank-data.
bank_data_flags=()
[[ $BANK_LOCAL_DATA -eq 1 ]] && bank_data_flags=(--dataseg "BANK${bank_n}")
run "$SDCC" "${CC_FLAGS[@]}" \ run "$SDCC" "${CC_FLAGS[@]}" \
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \ --codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" \
"${bank_data_flags[@]}" \
-c -o "$rel" "$bank_src" -c -o "$rel" "$bank_src"
BANK_RELS+=("$rel") BANK_RELS+=("$rel")
# Virtual address: bank_n in upper byte, BANK_LOW16 in low half. # Virtual address: bank_n in upper byte, BANK_LOW16 in low half.
Binary file not shown.
+893
View File
@@ -0,0 +1,893 @@
# Функции BIOS v2.12
### Вызов функций BIOS
Вызов функций BIOS осуществляется из ассемблерного кода.
Номер функции задается в регистре C процессора. В остальные регистры,
при необходимости, загружаются входные параметры функции. После исполнения
функции происходит возврат в программу, из которой произошел вызов функции.
Установленный флаг CF (CF=1) означает, что работа функции произошла с
ошибкой. В некоторых регистрах передаются выходные параметры.
Ниже приведены таблицы входных и выходных параметров для каждой функции:
- Функции работы с памятью
- Функции управления 'железом'
- Функции управления окнами и режимами экрана
- Функции вывода текста на экран
- Графические функции
- Функции работы с жесткими дисками и дисководами
При работе функций BIOS необходимо что бы стек находился в области
8000h..0BFFFh, так как часть функций использует переключение страниц
PAGE1 и PAGE3.
Вызов функций BIOS возможен в различных конфигурациях компьютера Sprinter.
Конфигурация Spectrum: вызов функций BIOS осуществляется через 3D13h.
При этом работают и все функции TR-DOS.
Конфигурации Sprinter: вызов функций BIOS осуществляется через RST 18h
при подключенном системном ПЗУ.
Для постоянного подключения системного ПЗУ можно воспользоваться
такой последовательностью команд:
```asm
LD A,0
OUT (07Ch),A
```
После ее исполнения в 0-м окне Z80 будет включено ПЗУ BIOSа и программа
может вызывать функции BIOSа через RST 18h.
Отключение ПЗУ BIOSа из нулевого окна Z80 производится следующей
последовательностью команд:
```asm
LD A,0
OUT (03Ch),A
```
При необходимости, функции BIOSа могут быть вызваны программой, находящейся
в ОЗУ непосредственно в нулевом окне Z80. Для этого надо установить в
адресе 0008h следующий код:
```asm
ADDRESS_0008h:
PUSH AF
LD A,0
OUT (7Ch),A
POP AF
RET
```
После этого BIOS можно вызывать командой RST 8. (Функции TR-DOS, так
же как и в случае RST 18 остаются недоступны.) Вызывая программы таким
образом, через RST 8, следует помнить что адреса 3FFFh..0000h после входа
в BIOS будут содержать код ПЗУ, поэтому, если фунция использует данные в
ОЗУ, они должны находиться в других адресах.
Оптимизация программы для RST 8 недопустима, так как в ПЗУ, для
обратного переключения, стоит такая же программа, только порт 3Ch для
отключения ПЗУ BIOSа.
Вызов функций BIOS в exe-файлах, вызываемых с помощью операционной
системы Estex, производится командой RST 8. Необходимая программа в адресе
0008h уже имеется в блоке кода ОС Estex.
### Функции работы с памятью
**0C0h (192) EMM_FN0 Определение объемов ОЗУ**
Значение регистров на входе:
C=0C0h
Значение регистров на выходе:
HL - общий объем ОЗУ в страницах по 16k
BC - объем свободного ОЗУ в страницах по 16k
**0C1h (193) EMM_FN1 Инициализация распределения памяти**
Стирается вся информация о выделенных ранее блоках ОЗУ.
Блоки с системной информацией и первые 256K ОЗУ объявляются занятыми.
Значение регистров на входе:
C=0C1h
Значение регистров на выходе:
нет
**0C2h (194) EMM_FN2 Выделение блока ОЗУ**
Значение регистров на входе:
C=0C2h
B - число запрашиваемых страниц
Значение регистров на выходе:
CF=0 - нормальное завершение - A - идентификатор блока
CF=1 - ошибка - A=1 - не хватает памяти
**0C3h (195) EMM_FN3 Освободить блок ОЗУ**
Значение регистров на входе:
C=0C3h
A - идентификатор блока
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неправильный идентификатор
идентификатор не всегда отслеживается правильно
**0C4h (196) EMM_FN4 Получить физический номер страницы из блока памяти**
Значение регистров на входе:
C=0C4h
A - идентификатор блока
B - логическая номер страницы в блоке
Значение регистров на выходе:
CF=0 - A - логический номер страницы
CF=1 - ошибка:
A=0 - блок не существует
A=255 - запрашиваемый номер страницы слишком велик
**0C5h (197) EMM_FN5 Получить список физических страниц блока**
Значение регистров на входе:
C=0C5h
A - идентификатор блока
HL - буфер 256 байт для размещения списка страниц
Значение регистров на выходе:
CF=0 - нормальное завершение:
B - число страниц в блоке
HL - тот же адрес буфера, в буфере список физических
страниц по порядку, заканчивающийся байтом 0FFh
CF=1 - неверный идентификатор блока; старая информация в буфере
может быть затерта
**0C6h (198) EMM_FN6 Получение адресов портов окон**
Примечание по использованию функции получения адресов портов окон (0C6h):
>Cледует хотя бы один раз вызвать эти функции и сравнить адреса
>портов с теми, что используются в программе и, если они не совпадают,
>выдать соответствующее предупреждение. В данный момент эти порты
>таковы: PAGE0=82h, PAGE1=0A2h, PAGE2=0C2h, PAGE3=0E2h
Значение регистров на входе:
C=0C6h
A - номер окна процессора - 0,1,2 или 3
Значение регистров на выходе:
CF=0 - нормальное завершение
C - 8-битный адрес порта окна
B - физический номер подключенной в окно страницы
CF=1 - ошибка - неверный номер окна
**0C7h (199) EMM_FN7 Получить номер следующей страницы блока**
Примечание по использованию функции EMM_FN7 (0C7h):
> Информация о распределении памяти хранится в виде RAM Allocation Table,
> похожей на дисковый FAT. Поэтому нахождение физического номера
> следующей страницы по предыдущему физическому номеру происходит
> значительно быстрее, чем поиск по увеличенному на единицу логическому
> номеру.
Значение регистров на входе:
C=0C7h
A - физическая страница
Значение регистров на выходе:
CF=0 - нормальное завершение
A - следуюшая физическая страница блока
A=0FFh - индицирует конец блока
CF=1 - ошибка - страница не принадлежит никакому блоку.
фактически, это означает, что она свободна.
**09Eh (158) EMM_FN8 Слияние блоков**
Значение регистров на входе:
A - идентификатор блока 1
B - идентификатор блока 2
Значение регистров на выходе:
CF=0 - нормальное завершение
A - идентификатор объединенного блока
CF=1 - ошибка - неверный идентификатор блока
**09Dh (157) EMM_FN9 Разделение блока**
Значение регистров на входе:
C=09Dh
A - идентификатор блока
B - новая длина блока
Значение регистров на выходе:
CF=0 - нормальное завершение
A - идентификатор блока результата
B - идентификатор блока остатка
CF=1 - ошибка - неверный идентификатор блока
### Функции управления 'железом'
**0EFh (239) FN_VERSION Выдача информации о версии BIOSа и железа.**
Значение регистров на входе:
C=0EFh
HL - буфер, куда будет помещена ASCII строка с несколькими
полями, номером версии BIOS и названием модели
компьютера. Конец строки отмечен двойным нулем.
Значение регистров на выходе:
CF=0 - нормальное завершение
HL - тот же буфер с записанной строкой.
DE - версия BIOSа
BC - версия железа подробности ниже
A - количество полей в буфере (в данный момент - 2)
Первое поле - версия BIOS.
Второе - название модели компьютера.
CF=1 - ошибка - Очень старая версия,
не имеющая данной функции
**0F2h (242) FN_SICF=0 Установка синхронизации, очистка страницы режима экрана**
Значение регистров на входе:
C=0F2h
A - режим синхронизации
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный номер режима синхронизации
### Функции работы с CMOS данными и Turbo режимом
Комментарий к функциям CMOS (0F5h-0F7h):
> Функции CMOS_RD, CMOS_WR, CMOS_TEST работают всегда. Если в машине нет
> микросхемы CMOS, то эмулируется ее память. Наличие микросхемы
> определяется функцией CMOS_TEST.
**0F5h (245) CMOS_TEST Проверить наличие CMOS**
Значение регистров на входе:
C=0F5h
Значение регистров на выходе:
CF=0 - часы есть
CF=1 - часов нет
**0F6h (246) CMOS_RD Читать из регистра CMOS**
Значение регистров на входе:
C=0F6h
D - номер регистра CMOS
Значение регистров на выходе:
A - считанные данные
CF=0 - часы есть
CF=1 - часов нет
**0F7h (247) CMOS_WR Писать в регистр CMOS**
Значение регистров на входе:
C=0F7h
D - номер регистра CMOS
A - записываемые данные
Значение регистров на выходе:
CF=0 - часы есть
CF=1 - часов нет
**08Fh (143) FN_TURBO Функция управления турбо режимом.**
Комментарий к функции FN_TURBO (08Fh):
Переключение режима турбо может не произойти, если прошивка не
поддерживает это переключение. При этом ошибки не происходит. Так
же, переключение режима TURBO блокируется кнопкой "Turbo" в
режиме Turbo-OFF
Значение регистров на входе:
C=08Fh
A - режим турбо: 2 - off, 3 - on
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный режим турбо
### Функции управления окнами и режимами экрана
**0B0h (176) WIN_OPEN Функция открытия окна.**
Значение регистров на входе:
C=0B0h
IX - описатель окна
E - флаги окна:
бит 0 указывает какую страницу режима включать после
исполнения функции;
бит 4 указывает на какой странице режима открывать окно
Значение регистров на выходе:
CF=0 - нормальное завершение:
A - идентификатор окна
CF=1 - ошибка слишком много окон
**0B1h (177) WIN_CLOSE закрытие окна**
Значение регистров на входе:
C=0B1h
A - идентификатор окна
Значение регистров на выходе:
CF=0 - успешное завершение
CF=1 - ошибка - неверный идентификатор
Окно с номером 0 никогда не закрывается и попытка
закрытия приводит к ошибке
**0B2h (178) WIN_COPY_WIN Копирование данных текстового окна в память (запоминание окна)**
При работе этой функции через RST 18h или RST 8, обязателен запрет
прерываний, так как функция пользуется стеком для ускорения своей работы.
Значение регистров на входе:
C=0B2h
A - идентификатор глобального окна
H - размер окна в символах по вертикали
L - размер окна в символах по горизонтали
D - вертикальное положение окна в глобальном окне
E - горизонтальное положение окна в глобальном окне
IX - адрес буфера для запоминания данных
адрес буфера указывается для окна 0C000h если адрес
указан с 8000h, номер страницы буфера не действителен
ниже 8000h адрес указывать нельзя
A' - страница буфера для данных окна эта страница должна
принадлежать программе
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка - неверный идентификатор окна
**0B3h (179) WIN_RESTORE_WIN Копирование из памяти в текстовое окно (восстановление окна)**
При работе этой функции через RST 18h или RST 8, обязателен запрет
прерываний, так как функция пользуется стеком для ускорения своей работы.
Значение регистров на входе:
C=0B2h
A - идентификатор глобального окна
H - размер окна в символах по вертикали
L - размер окна в символах по горизонтали
D - вертикальное положение окна в глобальном окне
E - горизонтальное положение окна в глобальном окне
IX - адрес буфера для запоминания данных
адрес буфера указывается для окна 0C000h если адрес
указан с 8000h, номер страницы буфера не действителен
ниже 8000h адрес указывать нельзя
A' - страница буфера для данных окна эта страница должна
принадлежать программе
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка - неверный идентификатор окна
**0B4h (180) WIN_GET_SYM Взять символ с экрана**
Значение регистров на входе:
C=0B4h
A - идентификатор окна
DE - положение символа в окне:
D - вертикаль, E - горизонталь
Значение регистров на выходе:
CF=0 - нормальное завершение
L - символ, H - атрибут,
B - знакогенератор
CF=1 - ошибка неверный идентификатор окна
**0B5h (181) WIN_PUT_SYM Положить символ на экран**
Значение регистров на входе:
C=0B5h
A - идентификатор окна
DE - положение символа в окне:
D - вертикаль, E - горизонталь
L - символ, H - атрибут символа
B - знакогенератор
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка неверный идентификатор окна
**0B6h (182) WIN_SET_ZG установка знакогенератора**
Значение регистров на входе:
C=0B6h
A - системный номер знакогенератора
DE - указатель на 2Kb блок данных знакогенератора
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка (старая версия, нет функции)
**0B7h (183) WIN_MOVE_WIN Перемещение окна**
При работе этой функции через RST 18h или RST 8, обязателен запрет
прерываний, так как функция пользуется стеком для ускорения своей работы.
Значение регистров на входе:
C=0B7h
A - идентификатор глобального окна
H - размер локального окна по вертикали в символах
L - размер локального окна по горизонтали в символах
D - положение локального окна по вертикали в символах
E - положение локального окна по горизонтали в символах
IX - новое положение локального окна (подобно DE)
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка - неверный идентификатор окна
**0B8h (184) WIN_GET_ZG Получение знакогенератора**
Значение регистров на входе:
C=0B8h
DE - адрес, куда будет загружено 2kb знакогенератора
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка (старая версия, нет функции)
### Функции вывода текста на экран
**081h (129) LP_PRINT_ALL Печать символов с атрибутом**
На экран выводится строка из B одинаковых символов
Значение регистров на входе:
C=081h
A - символ
E - атрибут
B - число выводимых символов
регистры HL,IX - сохраняются
Значение регистров на выходе:
CF=0 - всегда
**082h (131) LP_PRINT_SYM Вывод символов на экран без атрибута**
На экран выводится строка из B одинаковых символов атрибут
остается тот, который был на экране
Значение регистров на входе:
C=082h
A - символ
B - число выводимых символов
регистры HL,IX - сохраняются
Значение регистров на выходе:
CF=0 - всегда
**083h (131) LP_PRINT_ATR печать атрибутов**
На экран выводится строка из B одинаковых атрибутов.
Символы не меняются.
Значение регистров на входе:
C=083h
E - атрибут
B - число выводимых символов
регистры HL,IX - сохраняются
Значение регистров на выходе:
CF=0 - всегда
**084h (132) LP_SET_PLACE Установка текущего знакоместа в окне**
Позиция печати устанавливается в соответстии с регистром DE
Значение регистров на входе:
C=084h
E - положение символа по горизонтали
D - номер символа по вертикали
Превышение границ приводит не к ошибке, а к переустановке
с начала, за вычетом полного размера окна
Значение регистров на выходе:
CF=0 - всегда
**085h (133) LP_PRINT_LN Вывод строки символов на экран с текущего знакоместа**
Значение регистров на входе:
C=085h
HL - адрес строки должен быть между 04000h и 0BFFFh
E - атрибут, с которым будет выведена строка
B - длина выводимой строки
Значение регистров на выходе:
CF=0 - всегда
**086h (134) LP_PRINT_LN2 Вывод строки символов на экран без атрибутов**
Значение регистров на входе:
C=086h
HL - адрес строки должен быть между 04000h и 0BFFFh
B - длина выводимой строки
Значение регистров на выходе:
CF=0 - всегда
**087h (135) LP_PRINT_LN3 Вывод строки символов до разделителя**
После разделителя выводятся пробелы, что бы вывести B символов
Значение регистров на входе:
C=087h
HL - адрес строки должен быть между 04000h и 0BFFFh
E - атрибут, с которым будет выведена строка
B - длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**088h (136) LP_PRINT_LN4 Вывод строки символов до разделителя, без атрибутов**
символы из выводятся на экран, пока не встретится символ равный D,
далее печатаются пробелы, как дополнение строки до B символов.
Атрибуты не изменяются.
Значение регистров на входе:
C=088h
HL - адрес строки должен быть между 04000h и 0BFFFh
B - длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**089h (137) LP_CLS_WIN Очистка экрана**
Выполнение производится выводом пробелов с заданным атрибутом
Значение регистров на входе:
C=089h
DE - положение локального окна
H - размер в символах локального окна по вертикали
L - размер в символах локального окна по горизонтали
B - атрибут очистки
Значение регистров на выходе:
CF=0 - всегда
**08Ah (138) LP_SCROLL_UD Скроллинг части глобального окна вверх/вниз**
Скроллируются полные строки глобального окна
Значение регистров на входе:
C=08Ah
B - тип скроллинга: 1 - вверх; 2 - вниз
D - начальная строка скроллинга
E - число скроллируемых строк
Значение регистров на выходе:
CF=0 - всегда
**08Bh (139) LP_PRINT_LN5 Вывод строки символов на экран до разделителя**
После разделителя вывод останавливается
Значение регистров на входе:
HL - адрес строки должен быть между 04000h и 0BFFFh
E - атрибут, с которым будет выведена строка
B - максимальная длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**08Ch (140) LP_PRINT_LN6 Вывод строки символов на экран до разделителя без атрибутов**
После разделителя вывод останавливается.
Значение регистров на входе:
C=08Ch
HL - адрес строки должен быть между 04000h и 0BFFFh
B - максимальная длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**08Dh (141) LP_CLS_WIN2 Очистка экрана с указанием символа заполнения**
Значение регистров на входе:
C=08Dh
DE - положение локального окна
H - размер в символах локального окна по вертикали
L - размер в символах локального окна по горизонтали
B - атрибут очистки
A - символ очистки
Значение регистров на выходе:
CF=0 - всегда
**08Eh (142) LP_GET_PLACE Получить текущее положение вывода на экран**
Значение регистров на входе:
C=08Eh
Значение регистров на выходе:
CF=0 - всегда
DE - координаты, в которых будет напечатан
следующий символ:
D - вертикаль, E - горизонталь
### Графические функции
**0A1h (161) PIC_POINT Установить точку**
Значение регистров на входе:
C=0A1h
DE - координата по вертикали (пиксели)
HL - координата по горизонтали (пиксели)
Координаты считаются от верхнего левого угла экрана
A - идентификатор окна
B - цвет точки
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный идентификатор окна
**0A4h (164) PIC_SET_PAL Установка палитры**
Значение регистров на входе:
C=0A4h
HL - данные палитры
E - номер начального цвета
D - количество устанавливаемых цветов
B - маска при установке палитры.
Для нормального режима должнa быть 0FFh
A - номер палитры 0..15; от 8 до 15 - резервные
Значение регистров на выходе:
CF=0 - всегда
**0A6h (166) SET_PAL_INIT Установка внутренней палитры**
Значение регистров на входе:
C=0A6h
A - страница палитры
B - номер палитры:
B=2 - установка спектрумовской палитры
B=1 - установка графической плаитры
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный номер палитры
### Функции работы с жесткими дисками и дисководами
**50h,52h,53h (80,82,83) Зарезервированы**
Значение регистров на входе и на выходе:
нет
**51h (81) DRV_RESET Сброс контроллера и настройка на диск**
Значение регистров на входе:
C=51h
A - номер и тип устройства
бит 7..4 - тип устройства:
#0x - FDD
#6x - RAM-DISK
#8x - HDD
#Cx - CD-ROM
бит 3..0 - номер устройства
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка, нет диска или нет устройства
**54h (84) DRV_VERIFY Проверка секторов**
Проверка внутренняя на совпадение ECC
Значение регистров на входе:
C=54h
A - номер и тип устройства (см. выше)
HL:IX - номер сектора (IX - младшая часть номера сектора)
B - количество проверяемых секторов
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - проверка с ошибкой
**55h (85) DRV_READ Чтение с устройства**
Значение регистров на входе:
C=55h
A - номер и тип устройства (см. выше)
HL:IX - номер сектора (IX - младшая часть номера сектора)
B - количество секторов
DE - адрес буфера для данных
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка чтения
HL:IX - номер сектора + кол-во прочитанных секторов
DE - адрес буфера для данных + (кол-во прочитанных секторов * размер сектора)
**56h (86) DRV_WRITE Запись на устройства**
Значение регистров на входе:
C=56h
A - номер и тип устройства (см. выше)
HL:IX - номер сектора (IX - младшая часть номера сектора)
B - количество секторов
DE - адрес буфера данных для записи
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка записи
HL:IX - номер сектора + кол-во прочитанных секторов
DE - адрес буфера для данных + (кол-во прочитанных секторов * размер сектора)
**57h (87) DRV_DETECT Определение параметров устройства**
Значение регистров на входе:
C=57h
A - номер и тип устройства (см. выше)
Значение регистров на выходе:
CF=0 - нормальное завершение
A - bit7=0 диск 720Кb
bit7=1 диск 1.44Mb
CF=1 - нет устройства или нет носителя
**58h (88) DRV_GET_PAR Получить параметры носителя**
Значение регистров на входе:
C=58h
A - номер и тип устройства (см. выше)
Значение регистров на выходе:
CF=0 - нормальное завершение
L - число секторов
H - число головок
DE - количество цилиндров
если HL=DE=0FFFFh - устройства нет
IX - размер сектора в байтах
B - доп. параметры для дискет:
бит7 - тип 1.44Mb/720Kb
CF=1 - нет устройства
**59h (89) DRV_SET_PAR Установить параметры носителя**
Значение регистров на входе:
A - номер и тип устройства (см. выше)
L - число секторов
H - число головок
DE - количество цилиндров
IX - размер сектора в байтах
B - доп. параметры для дискет
бит7 - тип 1.44Mb/720Kb
Значение регистров на выходе:
CF=0 - нормальное завершение
**5Ah (90) EXT_VERSION Номер версии дисковой спецификации.**
Значение регистров на входе:
C=5Ah
Значение регистров на выходе:
CF=0 - нормальное завершение
D - версия
E - модификация
CF=1 - ошибка
**5Fh (95) DRV_LIST Список дисковых устройств**
Значение регистров на входе:
C=5Fh
IX - буфер для списка устройств
Значение регистров на выходе:
CF=0 - нормальное завершение
В буфере список дисков в формате:
IX+0 - размер заполненого буфера
IX+1 - кол-во устройств FDD
IX+2 - кол-во устройств HDD
IX+3 - кол-во устройств CD DRIVE
IX+4 - #00 - конец списка, иначе кол-во устройств нового типа
### Примечания и комментарии.
##### *Примечание по использованию функции получения адресов портов окон (0C6h).*
Cледует хотя бы один раз вызвать эти функции и сравнить адреса портов с
теми, что используются в программе и, если они не совпадают, выдать
соответствующее предупреждение. В данный момент эти порты таковы:
PAGE0=82h, PAGE1=0A2h, PAGE2=0C2h, PAGE3=0E2h
##### *Примечание по использованию функции EMM_FN7 (0C7h).*
Информация о распределении памяти хранится в виде RAM Allocation Table,
похожей на дисковый FAT. Поэтому нахождение физического номера следующей
страницы по предыдущему физическому номеру происходит значительно быстрее,
чем поиск по увеличенному на единицу логическому номеру.
##### *Комментарий к функции FN_VERSION (0EFh).*
Значения регистра BC на выходе и соответствующая ему конфигурация
BC=FFFF - Не определено
BC=FFFE - Конфигурация Spectrum, режим Sprinter ZX
BC=FFFD - Конфигурация Sprinter
BC=FFFC - Зарезервировано
BC=FFFB - Зарезервировано
BC=FFFA - Зарезервировано
BC=FFF9 - Зарезервировано
Иные значения BC - новые прошивки.
##### *Комментарий к функциям CMOS (0F5h-0F7h)*
Функции CMOS_RD, CMOS_WR, CMOS_TEST работают всегда. Если в машине нет
микросхемы CMOS, то эмулируется ее память. Наличие микросхемы
определяется функцией CMOS_TEST.
##### *Комментарий к функции FN_TURBO (08Fh)*
Переключение режима турбо может не произойти, если прошивка не
поддерживает это переключение. При этом ошибки не происходит. Так же,
переключение режима TURBO блокируется кнопкой "Turbo" в режиме Turbo-OFF
##### *Комментарий к функциям печати текста.*
Эти функции работают с текущим окном, которым всегда является последнее
открытое окно. К графическому экрану функции печати текста не применимы.
##### *Описатель окна.*
Для открытия окон используется 32-хбайтовый описатель окна (дескриптор),
адрес которого указывается в регистре IX.
IX - 32-хбайтовый описатель окна
(IX+0) - горизонтальный размер окна в знакоместах
(IX+1) - вертикальный размер в знакоместах
(IX+2) - положение окна по горизонтали на экране в знакоместах
(IX+3) - положение окна по вертикали на экране в знакоместах
(IX+4) - режим знакоместа
bit4=1 - text_mode bit4=0 - graf_mode
bit5=0 - 16, bit5=1 - 8 точек в знакоместе
graf_mode bit3..0 - не существенны
bit7..6 - номер палитры
text_mode bit7..6,3..0 - номер знакогенератора
исключение: bit7..6=B"11" - бордер
(IX+5) - дополнительный режим знакоместа
bit0=1 - указывает на включение спектрумовской
адресации экрана
(IX+6) - положение по X в поле графики (по знакоместам)
(IX+7) - положение по Y в поле графики (по знакоместам)
разъяснения о положении в поле графики - ниже
(IX+8..31) - зарезервировано (переменные окна)
в этих байтах должны быть нули
При открытии окна описатель копируется в системную страницу ОЗУ и
программа может не сохранять его. Что бы описатель окна не потерялся,
программа получает идентификатор окна. Он же идентификатор глобального
окна. В функциях запоминания, восстановления, перемещения, а так же
функциях стирания, скроллинга и т.п. идентификатор окна определяет
область экрана, относительно которой производится работа с локальными
окнами. Подразумеваются локальные окна в смысле "окно в окне".
Идентификатор окна определяет глобальное окно, отнoсительно которого
адресуются локальные. В части функций глобальное окно определяется по
умолчанию, как последнее, с которым производились действия с явным
указанием идентификатора.
В данный момент BIOS хранит только один описатель окна - последний,
с которым была произведена функция открытия. Идентификатор окна
выставляется в 0. В дальнейшем планитруется разработка функций со
множеством окон, потому, во избежание неприятностей в будущем, при
работе с окнами, программисту следует запоминать идентификатор окна
и пользоваться этим значением при работе с ним.
Типы стандартных окон:
0 - окно 32x24 в формате ZX Spectrum
1 - текстовое окно 64x24
2 - текстовое окно 40x32
3 - текстовое окно 80x32
4 - окно в формате ZX Spectrum, HL - положение окна на экране в знакоместах
5 - текстовое окно 64x24, HL - положение окна на экране в знакоместах
6 - текстовое окно 40x32, HL - положение окна на экране в знакоместах
7 - текстовое окно 80x32, HL - положение окна на экране в знакоместах
8 - графическое окно 0, HL - положение окна на экране
9 - графическое окно 1, HL - положение окна на экране
Данные палитры должны представлять собой список приблизительно такого вида:
DB blue1,green1,red1,0
DB blue2,green2,red2,0
.....................
DB blueN,greenN,redN,0
N - количество цветов. Значеное равное 0 соответствует 256-ти цветам.
При записи в видео-ОЗУ все данные предварительно проходят функцию AND
со значением регистра маски - B.
Страницы палитры 0..3 соответствуют графическим режимам. Для вывода в
соответствующей палитре нужно задать соответствующее значение bit7..6 в
байте режима знакоместа
Страницы 4..7 соответствуют текстовому режиму и спектрумовскому режиму.
В странице 4 задается цвет PAPER для каждого атрибута. В странице 5
задается цвет INK для каждого атрибута. В странице 6 задается цвет PAPER,
которым он будет моргать в режиме FLASH В странице 7 задается цвет INK,
которым он будет моргать в режиме FLASH. Таким образом, для каждого из
256-ти атрибутов задается четыре цвета если цвета 4,5 совпадают с
цветами 6,7 то режим FLASH оказывается отключенным. Для его включения
в спектрумовском режиме надо поменять местами цвета 6 и 7. Если надо
включить FLASH в режим IBM-CGA, следует установить цвета 6 и 7
одинаковыми и равными цвету 4. По сути режим FLASH всегда включен, и
на экране постоянно меняются цвета PAPER с 4-го на 6-й, а цвета INK с
5 на 7-й. Если эти пары цветов для атрибута знакоместа устанавливаются
одинаковыми, то FLASH в этом месте не виден.
##### *Комментарий к функциям работы с устройствами хранения информации.*
В этих функциях в регистре A обычно задается номер и тип устройства:
бит 0..3 - номер устройства
бит 4..7 - тип устройства:
0 - дисковод
6 - ram-disk
8 - HDD
C - CD-ROM
остальные номера не используются
А так же задаются: старшая часть номера сектора в регисте HL,
младшая часть номера сектора в регистре IX.
@@ -0,0 +1,901 @@
# Функции BIOS v3.00
### Вызов функций BIOS
**Вызов функций BIOS осуществляется из ассемблерного кода.**
Номер функции задается в регистре C процессора. В остальные
регистры, при необходимости, загружаются входные параметры функции.
После исполнения функции происходит возврат в программу, из которой
произошел вызов функции. Установленный флаг CF (CF=1) означает, что
работа функции произошла с ошибкой. В некоторых регистрах передаются
выходные параметры.
Ниже приведены таблицы входных и выходных параметров для каждой функции:
- Функции работы с памятью
- Функции управления 'железом'
- Функции управления окнами и режимами экрана
- Функции вывода текста на экран
- Графические функции
- Функции работы с жесткими дисками и дисководами
При работе функций BIOS необходимо что бы стек находился в области
8000h..0BFFFh, так как часть функций использует переключение страниц
PAGE1 и PAGE3.
Вызов функций BIOS возможен в различных конфигурациях компьютера
Sprinter.
**Конфигурация Spectrum:** вызов функций BIOS осуществляется через
3D13h. При этом работают и все функции TR-DOS.
**Конфигурации Sprinter:** вызов функций BIOS осуществляется через RST
18h при подключенном системном ПЗУ.
Для постоянного подключения системного ПЗУ можно воспользоваться такой
последовательностью команд:
```asm
LD A,0
OUT (07Ch),A
```
После ее исполнения в 0-м окне Z80 будет включено ПЗУ BIOSа и программа
может вызывать функции BIOSа через RST 18h.
Отключение ПЗУ BIOSа из нулевого окна Z80 производится следующей
последовательностью команд:
```asm
LD A,0
OUT (03Ch),A
```
При необходимости, функции BIOSа могут быть вызваны программой,
находящейся в ОЗУ непосредственно в нулевом окне Z80. Для этого надо
установить в адресе 0008h следующий код:
```asm
ADDRESS_0008h:
PUSH AF
LD A,0
OUT (7Ch),A
POP AF
RET
```
После этого BIOS можно вызывать командой RST 8. (Функции TR-DOS, так
же как и в случае RST 18 остаются недоступны.) Вызывая программы таким
образом, через RST 8, следует помнить что адреса 3FFFh..0000h после
входа в BIOS будут содержать код ПЗУ, поэтому, если фунция использует
данные в ОЗУ, они должны находиться в других адресах.
Оптимизация программы для RST 8 недопустима, так как в ПЗУ, для
обратного переключения, стоит такая же программа, только порт 3Ch для
отключения ПЗУ BIOSа.
Вызов функций BIOS в exe-файлах, вызываемых с помощью операционной
системы Estex, производится командой RST 8. Необходимая программа в
адресе 0008h уже имеется в блоке кода ОС Estex.
### Функции работы с памятью
**0C0h (192) EMM_FN0 Определение объемов ОЗУ**
Значение регистров на входе:
C=0C0h
Значение регистров на выходе:
HL - общий объем ОЗУ в страницах по 16k
BC - объем свободного ОЗУ в страницах по 16k
**0C1h (193) EMM_FN1 Инициализация распределения памяти**
Стирается вся информация о выделенных ранее блоках ОЗУ.
Блоки с системной информацией и первые 256K ОЗУ объявляются занятыми.
Значение регистров на входе:
C=0C1h
Значение регистров на выходе:
нет
**0C2h (194) EMM_FN2 Выделение блока ОЗУ**
Значение регистров на входе:
C=0C2h
B - число запрашиваемых страниц
Значение регистров на выходе:
CF=0 - нормальное завершение - A - идентификатор блока
CF=1 - ошибка - A=1 - не хватает памяти
**0C3h (195) EMM_FN3 Освободить блок ОЗУ**
Значение регистров на входе:
C=0C3h
A - идентификатор блока
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неправильный идентификатор
идентификатор не всегда отслеживается правильно
**0C4h (196) EMM_FN4 Получить физический номер страницы из блока памяти**
Значение регистров на входе:
C=0C4h
A - идентификатор блока
B - логическая номер страницы в блоке
Значение регистров на выходе:
CF=0 - A - логический номер страницы
CF=1 - ошибка:
A=0 - блок не существует
A=255 - запрашиваемый номер страницы слишком велик
**0C5h (197) EMM_FN5 Получить список физических страниц блока**
Значение регистров на входе:
C=0C5h
A - идентификатор блока
HL - буфер 256 байт для размещения списка страниц
Значение регистров на выходе:
CF=0 - нормальное завершение:
B - число страниц в блоке
HL - тот же адрес буфера, в буфере список физических
страниц по порядку, заканчивающийся байтом 0FFh
CF=1 - неверный идентификатор блока; старая информация в буфере
может быть затерта
**0C6h (198) EMM_FN6 Получение адресов портов окон**
Примечание по использованию функции получения адресов портов окон (0C6h):
>Cледует хотя бы один раз вызвать эти функции и сравнить адреса
>портов с теми, что используются в программе и, если они не совпадают,
>выдать соответствующее предупреждение. В данный момент эти порты
>таковы: PAGE0=82h, PAGE1=0A2h, PAGE2=0C2h, PAGE3=0E2h
Значение регистров на входе:
C=0C6h
A - номер окна процессора - 0,1,2 или 3
Значение регистров на выходе:
CF=0 - нормальное завершение
C - 8-битный адрес порта окна
B - физический номер подключенной в окно страницы
CF=1 - ошибка - неверный номер окна
**0C7h (199) EMM_FN7 Получить номер следующей страницы блока**
Примечание по использованию функции EMM_FN7 (0C7h):
> Информация о распределении памяти хранится в виде RAM Allocation Table,
> похожей на дисковый FAT. Поэтому нахождение физического номера
> следующей страницы по предыдущему физическому номеру происходит
> значительно быстрее, чем поиск по увеличенному на единицу логическому
> номеру.
Значение регистров на входе:
C=0C7h
A - физическая страница
Значение регистров на выходе:
CF=0 - нормальное завершение
A - следуюшая физическая страница блока
A=0FFh - индицирует конец блока
CF=1 - ошибка - страница не принадлежит никакому блоку.
фактически, это означает, что она свободна.
**09Eh (158) EMM_FN8 Слияние блоков**
Значение регистров на входе:
A - идентификатор блока 1
B - идентификатор блока 2
Значение регистров на выходе:
CF=0 - нормальное завершение
A - идентификатор объединенного блока
CF=1 - ошибка - неверный идентификатор блока
**09Dh (157) EMM_FN9 Разделение блока**
Значение регистров на входе:
C=09Dh
A - идентификатор блока
B - новая длина блока
Значение регистров на выходе:
CF=0 - нормальное завершение
A - идентификатор блока результата
B - идентификатор блока остатка
CF=1 - ошибка - неверный идентификатор блока
### Функции управления 'железом'
**0EFh (239) FN_VERSION Выдача информации о версии BIOSа и железа.**
Значение регистров на входе:
C=0EFh
HL - буфер, куда будет помещена ASCII строка с несколькими
полями, номером версии BIOS и названием модели
компьютера. Конец строки отмечен двойным нулем.
Значение регистров на выходе:
CF=0 - нормальное завершение
HL - тот же буфер с записанной строкой.
DE - версия BIOSа
BC - версия железа подробности ниже
A - количество полей в буфере (в данный момент - 2)
Первое поле - версия BIOS.
Второе - название модели компьютера.
CF=1 - ошибка - Очень старая версия,
не имеющая данной функции
**0F2h (242) FN_SICF=0 Установка синхронизации, очистка страницы режима экрана**
Значение регистров на входе:
C=0F2h
A - режим синхронизации
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный номер режима синхронизации
### Функции работы с CMOS данными и Turbo режимом
Комментарий к функциям CMOS (0F5h-0F7h):
> Функции CMOS_RD, CMOS_WR, CMOS_TEST работают всегда. Если в машине нет
> микросхемы CMOS, то эмулируется ее память. Наличие микросхемы
> определяется функцией CMOS_TEST.
**0F5h (245) CMOS_TEST Проверить наличие CMOS**
Значение регистров на входе:
C=0F5h
Значение регистров на выходе:
CF=0 - часы есть
CF=1 - часов нет
**0F6h (246) CMOS_RD Читать из регистра CMOS**
Значение регистров на входе:
C=0F6h
D - номер регистра CMOS
Значение регистров на выходе:
A - считанные данные
CF=0 - часы есть
CF=1 - часов нет
**0F7h (247) CMOS_WR Писать в регистр CMOS**
Значение регистров на входе:
C=0F7h
D - номер регистра CMOS
A - записываемые данные
Значение регистров на выходе:
CF=0 - часы есть
CF=1 - часов нет
**08Fh (143) FN_TURBO Функция управления турбо режимом.**
Комментарий к функции FN_TURBO (08Fh):
Переключение режима турбо может не произойти, если прошивка не
поддерживает это переключение. При этом ошибки не происходит. Так
же, переключение режима TURBO блокируется кнопкой "Turbo" в
режиме Turbo-OFF
Значение регистров на входе:
C=08Fh
A - режим турбо: 2 - off, 3 - on
A - режим FDD: 12h - 720Kb, 13h - 1.44Mb
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный режим турбо
### Функции управления окнами и режимами экрана
**0B0h (176) WIN_OPEN Функция открытия окна.**
Значение регистров на входе:
C=0B0h
IX - описатель окна
E - флаги окна:
бит 0 указывает какую страницу режима включать после
исполнения функции;
бит 4 указывает на какой странице режима открывать окно
Значение регистров на выходе:
CF=0 - нормальное завершение:
A - идентификатор окна
CF=1 - ошибка слишком много окон
**0B1h (177) WIN_CLOSE закрытие окна**
Значение регистров на входе:
C=0B1h
A - идентификатор окна
Значение регистров на выходе:
CF=0 - успешное завершение
CF=1 - ошибка - неверный идентификатор
Окно с номером 0 никогда не закрывается и попытка
закрытия приводит к ошибке
**0B2h (178) WIN_COPY_WIN Копирование данных текстового окна в память (запоминание окна)**
При работе этой функции через RST 18h или RST 8, обязателен запрет
прерываний, так как функция пользуется стеком для ускорения своей работы.
Значение регистров на входе:
C=0B2h
A - идентификатор глобального окна
H - размер окна в символах по вертикали
L - размер окна в символах по горизонтали
D - вертикальное положение окна в глобальном окне
E - горизонтальное положение окна в глобальном окне
IX - адрес буфера для запоминания данных
адрес буфера указывается для окна 0C000h если адрес
указан с 8000h, номер страницы буфера не действителен
ниже 8000h адрес указывать нельзя
A' - страница буфера для данных окна эта страница должна
принадлежать программе
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка - неверный идентификатор окна
**0B3h (179) WIN_RESTORE_WIN Копирование из памяти в текстовое окно (восстановление окна)**
При работе этой функции через RST 18h или RST 8, обязателен запрет
прерываний, так как функция пользуется стеком для ускорения своей работы.
Значение регистров на входе:
C=0B2h
A - идентификатор глобального окна
H - размер окна в символах по вертикали
L - размер окна в символах по горизонтали
D - вертикальное положение окна в глобальном окне
E - горизонтальное положение окна в глобальном окне
IX - адрес буфера для запоминания данных
адрес буфера указывается для окна 0C000h если адрес
указан с 8000h, номер страницы буфера не действителен
ниже 8000h адрес указывать нельзя
A' - страница буфера для данных окна эта страница должна
принадлежать программе
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка - неверный идентификатор окна
**0B4h (180) WIN_GET_SYM Взять символ с экрана**
Значение регистров на входе:
C=0B4h
A - идентификатор окна
DE - положение символа в окне:
D - вертикаль, E - горизонталь
Значение регистров на выходе:
CF=0 - нормальное завершение
L - символ, H - атрибут,
B - знакогенератор
CF=1 - ошибка неверный идентификатор окна
**0B5h (181) WIN_PUT_SYM Положить символ на экран**
Значение регистров на входе:
C=0B5h
A - идентификатор окна
DE - положение символа в окне:
D - вертикаль, E - горизонталь
L - символ, H - атрибут символа
B - знакогенератор
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка неверный идентификатор окна
**0B6h (182) WIN_SET_ZG установка знакогенератора**
Значение регистров на входе:
C=0B6h
A - системный номер знакогенератора
DE - указатель на 2Kb блок данных знакогенератора
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка (старая версия, нет функции)
**0B7h (183) WIN_MOVE_WIN Перемещение окна**
При работе этой функции через RST 18h или RST 8, обязателен запрет
прерываний, так как функция пользуется стеком для ускорения своей работы.
Значение регистров на входе:
C=0B7h
A - идентификатор глобального окна
H - размер локального окна по вертикали в символах
L - размер локального окна по горизонтали в символах
D - положение локального окна по вертикали в символах
E - положение локального окна по горизонтали в символах
IX - новое положение локального окна (подобно DE)
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка - неверный идентификатор окна
**0B8h (184) WIN_GET_ZG Получение знакогенератора**
Значение регистров на входе:
C=0B8h
DE - адрес, куда будет загружено 2kb знакогенератора
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка (старая версия, нет функции)
### Функции вывода текста на экран
**081h (129) LP_PRINT_ALL Печать символов с атрибутом**
На экран выводится строка из B одинаковых символов
Значение регистров на входе:
C=081h
A - символ
E - атрибут
B - число выводимых символов
регистры HL,IX - сохраняются
Значение регистров на выходе:
CF=0 - всегда
**082h (131) LP_PRINT_SYM Вывод символов на экран без атрибута**
На экран выводится строка из B одинаковых символов атрибут
остается тот, который был на экране
Значение регистров на входе:
C=082h
A - символ
B - число выводимых символов
регистры HL,IX - сохраняются
Значение регистров на выходе:
CF=0 - всегда
**083h (131) LP_PRINT_ATR печать атрибутов**
На экран выводится строка из B одинаковых атрибутов.
Символы не меняются.
Значение регистров на входе:
C=083h
E - атрибут
B - число выводимых символов
регистры HL,IX - сохраняются
Значение регистров на выходе:
CF=0 - всегда
**084h (132) LP_SET_PLACE Установка текущего знакоместа в окне**
Позиция печати устанавливается в соответстии с регистром DE
Значение регистров на входе:
C=084h
E - положение символа по горизонтали
D - номер символа по вертикали
Превышение границ приводит не к ошибке, а к переустановке
с начала, за вычетом полного размера окна
Значение регистров на выходе:
CF=0 - всегда
**085h (133) LP_PRINT_LN Вывод строки символов на экран с текущего знакоместа**
Значение регистров на входе:
C=085h
HL - адрес строки должен быть между 04000h и 0BFFFh
E - атрибут, с которым будет выведена строка
B - длина выводимой строки
Значение регистров на выходе:
CF=0 - всегда
**086h (134) LP_PRINT_LN2 Вывод строки символов на экран без атрибутов**
Значение регистров на входе:
C=086h
HL - адрес строки должен быть между 04000h и 0BFFFh
B - длина выводимой строки
Значение регистров на выходе:
CF=0 - всегда
**087h (135) LP_PRINT_LN3 Вывод строки символов до разделителя**
После разделителя выводятся пробелы, что бы вывести B символов
Значение регистров на входе:
C=087h
HL - адрес строки должен быть между 04000h и 0BFFFh
E - атрибут, с которым будет выведена строка
B - длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**088h (136) LP_PRINT_LN4 Вывод строки символов до разделителя, без атрибутов**
символы из выводятся на экран, пока не встретится символ равный D,
далее печатаются пробелы, как дополнение строки до B символов.
Атрибуты не изменяются.
Значение регистров на входе:
C=088h
HL - адрес строки должен быть между 04000h и 0BFFFh
B - длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**089h (137) LP_CLS_WIN Очистка экрана**
Выполнение производится выводом пробелов с заданным атрибутом
Значение регистров на входе:
C=089h
DE - положение локального окна
H - размер в символах локального окна по вертикали
L - размер в символах локального окна по горизонтали
B - атрибут очистки
Значение регистров на выходе:
CF=0 - всегда
**08Ah (138) LP_SCROLL_UD Скроллинг части глобального окна вверх/вниз**
Скроллируются полные строки глобального окна
Значение регистров на входе:
C=08Ah
B - тип скроллинга: 1 - вверх; 2 - вниз
D - начальная строка скроллинга
E - число скроллируемых строк
Значение регистров на выходе:
CF=0 - всегда
**08Bh (139) LP_PRINT_LN5 Вывод строки символов на экран до разделителя**
После разделителя вывод останавливается
Значение регистров на входе:
HL - адрес строки должен быть между 04000h и 0BFFFh
E - атрибут, с которым будет выведена строка
B - максимальная длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**08Ch (140) LP_PRINT_LN6 Вывод строки символов на экран до разделителя без атрибутов**
После разделителя вывод останавливается.
Значение регистров на входе:
C=08Ch
HL - адрес строки должен быть между 04000h и 0BFFFh
B - максимальная длина выводимой строки
D - символ-разделитель, указывающий конец строки
Значение регистров на выходе:
CF=0 - всегда
**08Dh (141) LP_CLS_WIN2 Очистка экрана с указанием символа заполнения**
Значение регистров на входе:
C=08Dh
DE - положение локального окна
H - размер в символах локального окна по вертикали
L - размер в символах локального окна по горизонтали
B - атрибут очистки
A - символ очистки
Значение регистров на выходе:
CF=0 - всегда
**08Eh (142) LP_GET_PLACE Получить текущее положение вывода на экран**
Значение регистров на входе:
C=08Eh
Значение регистров на выходе:
CF=0 - всегда
DE - координаты, в которых будет напечатан
следующий символ:
D - вертикаль, E - горизонталь
### Графические функции
**0A1h (161) PIC_POINT Установить точку**
Значение регистров на входе:
C=0A1h
DE - координата по вертикали (пиксели)
HL - координата по горизонтали (пиксели)
Координаты считаются от верхнего левого угла экрана
A - идентификатор окна
B - цвет точки
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный идентификатор окна
**0A4h (164) PIC_SET_PAL Установка палитры**
Значение регистров на входе:
C=0A4h
HL - данные палитры
E - номер начального цвета
D - количество устанавливаемых цветов
B - маска при установке палитры.
Для нормального режима должнa быть 0FFh
A -
0..3 биты - значения 0..7 - номер палитры
- значения 8..15 - зарезервированы
4..6 биты - зарезервированы (установить в 0)
7 бит -
- значение 0 - установить
- значение 1 - загрузить палитру в память
Значение регистров на выходе:
CF=0 - всегда
**0A6h (166) SET_PAL_INIT Установка внутренней палитры**
Значение регистров на входе:
C=0A6h
A - страница палитры
B - номер палитры:
B=3 - установка текстовой палитры CGA
B=2 - установка спектрумовской палитры
B=1 - установка графической плаитры
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - неверный номер палитры
### Функции работы с жесткими дисками и дисководами
**50h,52h,53h (80,82,83) Зарезервированы**
Значение регистров на входе и на выходе:
нет
**51h (81) DRV_RESET Сброс контроллера и настройка на диск**
Значение регистров на входе:
C=51h
A - номер и тип устройства
бит 7..4 - тип устройства:
#0x - FDD
#6x - RAM-DISK
#8x - HDD
#Cx - CD-ROM
бит 3..0 - номер устройства
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка, нет диска или нет устройства
**54h (84) DRV_VERIFY Проверка секторов**
Проверка внутренняя на совпадение ECC
Значение регистров на входе:
C=54h
A - номер и тип устройства (см. выше)
HL:IX - номер сектора (IX - младшая часть номера сектора)
B - количество проверяемых секторов
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - проверка с ошибкой
**55h (85) DRV_READ Чтение с устройства**
Значение регистров на входе:
C=55h
A - номер и тип устройства (см. выше)
HL:IX - номер сектора (IX - младшая часть номера сектора)
B - количество секторов
DE - адрес буфера для данных
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка чтения
HL:IX - номер сектора + кол-во прочитанных секторов
DE - адрес буфера для данных + (кол-во прочитанных секторов * размер сектора)
**56h (86) DRV_WRITE Запись на устройства**
Значение регистров на входе:
C=56h
A - номер и тип устройства (см. выше)
HL:IX - номер сектора (IX - младшая часть номера сектора)
B - количество секторов
DE - адрес буфера данных для записи
Значение регистров на выходе:
CF=0 - нормальное завершение
CF=1 - ошибка записи
HL:IX - номер сектора + кол-во прочитанных секторов
DE - адрес буфера для данных + (кол-во прочитанных секторов * размер сектора)
**57h (87) DRV_DETECT Определение параметров устройства**
Значение регистров на входе:
C=57h
A - номер и тип устройства (см. выше)
Значение регистров на выходе:
CF=0 - нормальное завершение
A - bit7=0 диск 720Кb
bit7=1 диск 1.44Mb
CF=1 - нет устройства или нет носителя
**58h (88) DRV_GET_PAR Получить параметры носителя**
Значение регистров на входе:
C=58h
A - номер и тип устройства (см. выше)
Значение регистров на выходе:
CF=0 - нормальное завершение
L - число секторов
H - число головок
DE - количество цилиндров
если HL=DE=0FFFFh - устройства нет
IX - размер сектора в байтах
B - доп. параметры для дискет:
бит7 - тип 1.44Mb/720Kb
CF=1 - нет устройства
**59h (89) DRV_SET_PAR Установить параметры носителя**
Значение регистров на входе:
A - номер и тип устройства (см. выше)
L - число секторов
H - число головок
DE - количество цилиндров
IX - размер сектора в байтах
B - доп. параметры для дискет
бит7 - тип 1.44Mb/720Kb
Значение регистров на выходе:
CF=0 - нормальное завершение
**5Ah (90) EXT_VERSION Номер версии дисковой спецификации.**
Значение регистров на входе:
C=5Ah
Значение регистров на выходе:
CF=0 - нормальное завершение
D - версия
E - модификация
CF=1 - ошибка
**5Fh (95) DRV_LIST Список дисковых устройств**
Значение регистров на входе:
C=5Fh
IX - буфер для списка устройств
Значение регистров на выходе:
CF=0 - нормальное завершение
В буфере список дисков в формате:
IX+0 - размер заполненого буфера
IX+1 - кол-во устройств FDD
IX+2 - кол-во устройств HDD
IX+3 - кол-во устройств CD DRIVE
IX+4 - #00 - конец списка, иначе кол-во устройств нового типа
### Примечания и комментарии.
##### *Примечание по использованию функции получения адресов портов окон (0C6h).*
Cледует хотя бы один раз вызвать эти функции и сравнить адреса портов с
теми, что используются в программе и, если они не совпадают, выдать
соответствующее предупреждение. В данный момент эти порты таковы:
PAGE0=82h, PAGE1=0A2h, PAGE2=0C2h, PAGE3=0E2h
##### *Примечание по использованию функции EMM_FN7 (0C7h).*
Информация о распределении памяти хранится в виде RAM Allocation Table,
похожей на дисковый FAT. Поэтому нахождение физического номера следующей
страницы по предыдущему физическому номеру происходит значительно быстрее,
чем поиск по увеличенному на единицу логическому номеру.
##### *Комментарий к функции FN_VERSION (0EFh).*
Значения регистра BC на выходе и соответствующая ему конфигурация
BC=FFFF - Не определено
BC=FFFE - Конфигурация Spectrum, режим Sprinter ZX
BC=FFFD - Конфигурация Sprinter
BC=FFFC - Зарезервировано
BC=FFFB - Зарезервировано
BC=FFFA - Зарезервировано
BC=FFF9 - Зарезервировано
Иные значения BC - новые прошивки.
##### *Комментарий к функциям CMOS (0F5h-0F7h)*
Функции CMOS_RD, CMOS_WR, CMOS_TEST работают всегда. Если в машине нет
микросхемы CMOS, то эмулируется ее память. Наличие микросхемы
определяется функцией CMOS_TEST.
##### *Комментарий к функции FN_TURBO (08Fh)*
Переключение режима турбо может не произойти, если прошивка не
поддерживает это переключение. При этом ошибки не происходит. Так же,
переключение режима TURBO блокируется кнопкой "Turbo" в режиме Turbo-OFF
##### *Комментарий к функциям печати текста.*
Эти функции работают с текущим окном, которым всегда является последнее
открытое окно. К графическому экрану функции печати текста не применимы.
##### *Описатель окна.*
Для открытия окон используется 32-хбайтовый описатель окна (дескриптор),
адрес которого указывается в регистре IX.
IX - 32-хбайтовый описатель окна
(IX+0) - горизонтальный размер окна в знакоместах
(IX+1) - вертикальный размер в знакоместах
(IX+2) - положение окна по горизонтали на экране в знакоместах
(IX+3) - положение окна по вертикали на экране в знакоместах
(IX+4) - режим знакоместа
bit4=1 - text_mode bit4=0 - graf_mode
bit5=0 - 16, bit5=1 - 8 точек в знакоместе
graf_mode bit3..0 - не существенны
bit7..6 - номер палитры
text_mode bit7..6,3..0 - номер знакогенератора
исключение: bit7..6=B"11" - бордер
(IX+5) - дополнительный режим знакоместа
bit0=1 - указывает на включение спектрумовской
адресации экрана
(IX+6) - положение по X в поле графики (по знакоместам)
(IX+7) - положение по Y в поле графики (по знакоместам)
разъяснения о положении в поле графики - ниже
(IX+8..31) - зарезервировано (переменные окна)
в этих байтах должны быть нули
При открытии окна описатель копируется в системную страницу ОЗУ и
программа может не сохранять его. Что бы описатель окна не потерялся,
программа получает идентификатор окна. Он же идентификатор глобального
окна. В функциях запоминания, восстановления, перемещения, а так же
функциях стирания, скроллинга и т.п. идентификатор окна определяет
область экрана, относительно которой производится работа с локальными
окнами. Подразумеваются локальные окна в смысле "окно в окне".
Идентификатор окна определяет глобальное окно, отнoсительно которого
адресуются локальные. В части функций глобальное окно определяется по
умолчанию, как последнее, с которым производились действия с явным
указанием идентификатора.
В данный момент BIOS хранит только один описатель окна - последний,
с которым была произведена функция открытия. Идентификатор окна
выставляется в 0. В дальнейшем планитруется разработка функций со
множеством окон, потому, во избежание неприятностей в будущем, при
работе с окнами, программисту следует запоминать идентификатор окна
и пользоваться этим значением при работе с ним.
Типы стандартных окон:
0 - окно 32x24 в формате ZX Spectrum
1 - текстовое окно 64x24
2 - текстовое окно 40x32
3 - текстовое окно 80x32
4 - окно в формате ZX Spectrum, HL - положение окна на экране в знакоместах
5 - текстовое окно 64x24, HL - положение окна на экране в знакоместах
6 - текстовое окно 40x32, HL - положение окна на экране в знакоместах
7 - текстовое окно 80x32, HL - положение окна на экране в знакоместах
8 - графическое окно 0, HL - положение окна на экране
9 - графическое окно 1, HL - положение окна на экране
Данные палитры должны представлять собой список приблизительно такого вида:
DB blue1,green1,red1,0
DB blue2,green2,red2,0
.....................
DB blueN,greenN,redN,0
N - количество цветов. Значеное равное 0 соответствует 256-ти цветам.
При записи в видео-ОЗУ все данные предварительно проходят функцию AND
со значением регистра маски - B.
Страницы палитры 0..3 соответствуют графическим режимам. Для вывода в
соответствующей палитре нужно задать соответствующее значение bit7..6 в
байте режима знакоместа
Страницы 4..7 соответствуют текстовому режиму и спектрумовскому режиму.
В странице 4 задается цвет PAPER для каждого атрибута. В странице 5
задается цвет INK для каждого атрибута. В странице 6 задается цвет PAPER,
которым он будет моргать в режиме FLASH В странице 7 задается цвет INK,
которым он будет моргать в режиме FLASH. Таким образом, для каждого из
256-ти атрибутов задается четыре цвета если цвета 4,5 совпадают с
цветами 6,7 то режим FLASH оказывается отключенным. Для его включения
в спектрумовском режиме надо поменять местами цвета 6 и 7. Если надо
включить FLASH в режим IBM-CGA, следует установить цвета 6 и 7
одинаковыми и равными цвету 4. По сути режим FLASH всегда включен, и
на экране постоянно меняются цвета PAPER с 4-го на 6-й, а цвета INK с
5 на 7-й. Если эти пары цветов для атрибута знакоместа устанавливаются
одинаковыми, то FLASH в этом месте не виден.
##### *Комментарий к функциям работы с устройствами хранения информации.*
В этих функциях в регистре A обычно задается номер и тип устройства:
бит 0..3 - номер устройства
бит 4..7 - тип устройства:
0 - дисковод
6 - ram-disk
8 - HDD
C - CD-ROM
остальные номера не используются
Binary file not shown.
+155
View File
@@ -0,0 +1,155 @@
Estex: Disk SubSystem (DSS) Programming Guide
Table of Contents
1. Introducing
2. Identification of system functions
3. Disk devices functions
1. Introducing
This document contains the list of functions and concepts of
interaction with a disk subsystem.
DSS is a collection of very useful functions that reside in
DSS itself, ready for use by any your programs. These functions
are stored in library SYSTEM.DOS and allow management of files,
memory allocation, loading and execution of the programs.
File specification
The file specification is a string, containing a names of disk,
directories separated by a symbol "\" and name of file. The names
of disk drive and directories can be discard.
for example:
C:\TEXT\DOC\text.doc
A:file.txt
\TEXT\info.txt
The DSS used chars with colon suffix as names of disk devices
(A:, B:, C: etc.) The name of disk can be written down before
filename for specified disk there it placed.
For example: command
DIR C:DATFILE
searches for DATFILE in the current directory of disk C:.
When disk name not specified DSS used current disk. At start
DSS, the current disk is a disk whence was loaded DSS.
The filenames consist of two parts. The first part contain 8
chars of file name. The second part is not necessary and contain
3 chars of file type (also known as extentsion). At the writing
of filename, both parts are separated by char point.
For example: the names "NAME" and "NAME." is specified same file.
In the name don't allows symbols with codes less 32 and chars
. " / \ [ ] : | < > + = ; ,
As the subdirectories files too, their names are formed by
same way. The name of root directory always "\". And each
subdirectories contain two items with names "." and "..". The
name "." specified a current directory and name ".." specified
name of parent(uplevel) directory.
Some console commands and DSS functions allow to use global
symbols ? and * which can be used for filename templates.
The symbol ? means that any one char of filename. The
symbol * means that it char can be replaced by any symbols.
For example:
*.txt - means, all files with type "txt"
a??.* - means, files which contain three or less symbols and
first symbol is "a"
dc*.exe - means, files with type "exe" and began "dc"
File attributes
The each bit of byte attributes specified various attribute.
And it can be changed by DSS function.
bit 0 - Read only
bit 1 - Hidden
bit 2 - System
bit 3 - Volume label
bit 4 - Directory
bit 5 - Archive
bit 6 - Reserved
bit 7 - Reserved
Attribute "read only". When value is 1, file can be read,
but can't be written or deleted.
Attribute "hidden". When value is 1, DSS can't manipulate
with this file.
Attribute "system". Specified system file.
Attribute "volume label". In old version of MSDOS used for
specified volume laben, now it can be used for long filenames.
And must be 0 for compatibles.
Attribute "directory". When value is 1, means that this
file is directory.
Attribute "archive". This bite sets in 1 when DSS writing
in this file. It can be used in backup utilities for detect
changed files.
File handle
When any file are opened, DSS build File Control Block in
DSS working areas.
The Handle (and assigned file) identified by number which
returned DSS to program after file opening and used it in all
further DSS calls. In other words, when file is opening, the
program informs DSS his name and has taken back file handle.
Which used in further file operations.
All necessary information for working with file are placed
in DSS working areas.
2. Identification of system functions
00h (00) VERSION (Version of DSS)
input:
C - 00h
output:
D - version number
E - modification
The function return version number of DSS.
3. Disk devices functions
01h (01) CHDISK (Change current disk)
input:
A - disk number (0-A,1-B...)
C - 01h
output:
A - error code, if CF=1
A - number of disks, if CF=0
The function changes current disk device.
02h (02) CURDISK (Current disk number)
input:
C - 02h
output:
A - current disk number (0-A,1-B...)
The function returns number of current disk device.
03h (03) DSKINFO (Disk information)
input:
A - disk number (0-A,1-B...0FFh-current)
C - 03h
output:
A - error code, if CF=1
A - sectors per cluster, if CF=0
HL - clusters per disk
DE - free clusters
BC - bytes per sector
The function returns information about disk device
(capacity and free space).
for example:
LD C,03h ;Function DSKINFO
LD A,0FFh ;Information about current disk
RST 10h ;Execution of function
LD A,D ;There is a free
OR E ;space?
JR Z,NO_SPACE ;No, the disk is completely filled
09h (09) BOOTDSK (Number of boot disk)
input:
C - 09h
B = 0
output:
A - number of boot disk (0-A,1-B...)
The function returns number of boot disk device whence was loaded DSS.
+126
View File
@@ -0,0 +1,126 @@
Estex: Дисковая подсистема (DSS) – Обзор
1. Введение
2. Загрузка подсистемы
3. Системная консоль
4. Файловая система
1. Введение
Estex - операционная система компьютера Sprinter, включающая
в себя различные модули. Данный документ описывает модуль дисковой
подсистемы.
В DSS используется та же самая файловая система, как и в
MS-DOS FAT16 и полностью с ней совместима.
2. Загрузка подсистемы
После включения питания или сброса компьютера, BIOS считывает
первичный загрузчик с 1-го сектора загрузочного диска.
Если загрузка происходит с HDD или дискеты, то сначала загрузочный
сектор считывается в память и ему передается управление по загрузке
модуля дисковой подсистемы SYSTEM.DOS.
Затем выполняются следующие действия:
• инициализация дисковой подсистемы и вывод сообщения "Starting DOS..."
• загрузка системной консоли SYSTEM.EXE
• выполнение команд указанных в файле SYSTEM.BAT
Обычно файл SYSTEM.BAT содержит путь к программе файловой навигации
пользователя или другому часто используемому приложения.
Например "c:\fn\fn.exe".
Если во время загрузки вы хотите пропустить выполнение SYSTEM.BAT.
То вам следует нажать клавишу "SHIFT", как только появиться
сообщение "Starting DOS..." и удерживать пока не появится приглашение
консоли ("C:\>").
3. Системная консоль
В DSS многие задачи могут быть выполнены через интерфейс командной
строки называемой системной консолью. Основная задача консоли ввод
команд и их исполнение. Также она имеет ряд функций, которые выполняют
такие действия как управление файлами, перемещение по файловой структуре
каталогов, редактирование командной строки и переменных среды.
Системная консоль позволяет пользователю взаимодействовать с
операционной системой. Для DOS системная консоль это SYSTEM.EXE. Если вы
видите на экране приглашение командной строки (A:\> или C:\>), то это
означает что SYSTEM.EXE загружен и активизирован. Когда вы вводите
командную строку, командный процессор интерпретирует команду и выполняет
необходимые действия.
На сегодняшний день в консоли доступны следующие команды:
CD Displays the name of or changes the current directory.
CHDIR Displays the name of or changes the current directory.
CLS Clears the screen.
DATE Displays or sets the date.
DEL Deletes one or more files.
DIR Displays a list of files and subdirectories in a directory.
ECHO Displays messages, or turns command echoing on or off.
ERASE Deletes one or more files.
EXIT Quits the SYSTEM.EXE program (command interpreter).
HELP Provides Help information for console commands.
MD Creates a directory.
MKDIR Creates a directory.
PAUSE Suspends processing of a batch file and displays a message.
RD Removes a directory.
REM Records comments (remarks) in batch files or SYSTEM.BAT.
REN Renames a file or files.
RENAME Renames a file or files.
RMDIR Removes a directory.
TIME Displays or sets the system time.
VER Displays the System version.
4. Файловая система
Сейчас, в качестве файловой системы Estex использует FAT12 и FAT16.
С помощью файловой системы FAT (File Allocation Table) организуются
данные на винчестере и дискетах.
Для указания спецификации файла используется следующая форма:
[drive:][directory\]filename[.ext]
Файловая спецификация - это строка символов содержащая наименования
диска, директорий отделенных символом "\" и имя файла. Имена диска и
директории могут быть опущены, если требуемый файл расположен в текущей
директории.
Например:
C:\TEXT\DOC\text.doc
A:file.txt
\TEXT\info.txt
В DSS в качестве имен дисковых устройств используются буквы с
последующим символом двоеточия (A:, B:, C: и.т.д.) Имя диска может быть
набрано перед именем файла для указания диска, на котором он расположен.
Например:
команда
DIR C:TESTFILE
ищет TESTFILE в текущей директории диска C:.
Если имя диска не указанно используется текущий диск. После запуска DSS,
текущим диском является диск, с которого была загружена DSS.
Имена файлов состоят из двух частей. Первая часть может содержать
8 букв, цифр или следующие специальные символы:
$ % ' _ @ { } ~ ` ! # ( ).
Вторая часть не является обязательной и содержит любую комбинацию из трех
букв, цифр или специальных символов с предшествующей точкой (.).
Например имена "NAME" и "NAME." указывают на одинаковый файл.
В имени файла не допускаются символы с кодом меньше 32, а также символы
. " / \ [ ] : | < > + = ; ,
Поскольку директории также являются файлами их имена образуются по
тем же правилам.
Имя корневой директории всегда "\". И каждая поддиректория содержит
два элемента с именами "." and "..". Имя "." указывает на текущую
директорию, а имя ".." указывает на родительную (на уровень выше)
директорию.
Некоторые команды и функции DSS позволяют использовать глобальные
символы * и ? которые могут использоваться для задания шаблона имени
файла.
Символ ? означает любой один символ в имени файла.
Символ * означает, что он может быть заменен на любое количество
любых символов.
for example:
*.txt - означает, все файлы с типом "txt"
a??.* - означает, файлы содержащие три и менее символов и первый символ "a"
dc*.exe - означает, файлы с типом "exe" и начинающиеся на "dc"
В именах файлов не делается различий между заглавными и прописными
символами.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
Акселератор компьютера Sprinter.
Акселератор предназначен для ускорения операций по пересылке блоков данных в ОЗУ и видео-ОЗУ.
Акселератор позволяет:
- быстро заполнять горизонтальную или вертикальную линию длиной до 256 точек одним цветом (в режиме 640x256 - одинарную/двойную горизонтальную линию длиной до 512 точек)
- быстро копировать горизонтальную или вертикальную линию длиной до 256 точек (в режиме 640x256 - горизонтальную линию до 512 точек)
- проводить быстрые операции AND, OR, XOR с блоками памяти.
Акселератор не может работать с блоками данных ПЗУ и Быстрого-ОЗУ.
ОЗУ акселератора является частью внутреннего ОЗУ ППЛМ.
Операции по пересылке данных производятся путем записи блока данных в это
внутреннее ОЗУ, а затем копировании его в нужное место ОЗУ из ОЗУ акселератора.
Блок данных, записываемый в ОЗУ акселератора может иметь различную
длинну из диапазона 1..256 байт.
После одной записи копирование может производиться несколько раз и, таким
образом, можно производить заполнение экрана текстурами.
Для заполнения экрана одним цветом используется другой режим
акселератора. В нем вместо копируемого блока данных из внутреннего ОЗУ
производится запись данных с шины процессора, которые в этот момент не
изменяются.
Управление акселератором производится непосредственно из программы.
Для этого изпользуются команды процессора, которые, фактически, являются
операциями типа NOP.
LD B,B - выключить акселетарор.
LD D,D - включить акселератор в режим приема байта размера блока
далее следует команда типа LD A,dat, где dat и будет новым
размером блока. Если размер блока был установлен ранее,
его можно не устанавливать.
LD C,C - Операция Fill - заполнение одним байтом. Последующая
команда типа LD (HL),A приведет к заполнению указанного
ранее количества байт значением A
LD E,E - Операция Fill для графического экрана - заполнение
вертикальных линий.
LD H,H - rezerved
LD L,L - копирование блока. Последующая команда типа LD A,(HL)
приведет к заполнению ОЗУ акселератора данными из адреса (HL),
а команда типа LD (DE),A приведет к перезаписи данных из ОЗУ
акселератора в ОЗУ или видео-ОЗУ.
LD A,A - копирование блока для графического экрана подобна команде
LD L,L, но работает с вертикальными линиями экрана.
Пример использования акселератора:
; Считаем, что экранная страница уже открыта по адресу #C000
LD HL,#C040 ; адрес начала линии первого экрана
LD DE,#C180 ; адрес начала линии второго экрана
LD BC,#140 ; длина экрана по горизонтали
DI ; запретить прерывания для работы с акселератором
LD D,D ; включить акселератор на установку размера блока
LD A,0 ; установить размер блока - 256 байт
LD A,A ; установить акселератор на копирование
; вертикальных линий.
LDIR ; копировать
LD B,B ; выключить акселератор
EI ; включить прерывания
Эта часть программы произведет копирование всего содержимого первого экрана на другой.
Время исполения составляет примерно 26 милисекунд.
Дополнительные функции акселератора (AND, OR, XOR) работают таким же образом.
Для выполнения логических функций используются команды XOR (HL); OR (HL); AND (HL).
Пример кодирования блока в 256 байт.
LD HL,ADRES_1
LD DE,XOR_DAT
DI
LD D,D
LD A,0 ; число байт, которые надо закодировать
LD L,L
LD A,(DE) ; взять блок данных в ОЗУ акселератора
XOR (HL) ; произвести операцию XOR с данными акселератора
LD (HL),A ; запомнить в ОЗУ результат операции
LD B,B
EI
Скорость работы акселератора ограничивается только физической
скоростью работы основного ОЗУ. Определить примерное время работы команды с
акселератором можно по такой формуле:
Время работы = время работы команды без акселератора + время работы
акселератора
Время работы акселератора = число пересылаемых байт /7 микросекунд
Во время работы акселератора необходимо отключать прерывания, так как в этот момент
изменяется система команд процессора и программа на прерывании не сможет работать корректно.
@@ -0,0 +1,635 @@
ÜÜÜÜÜÜ
ÜÛ°°°°°°
Û°° Û°°
Û°°ÜÜÜÛ°°
Û°°°°°°°°
Û°° Û°°
Û°° Û°° àå¨â¥ªâãà  ª®¬¯ìîâ¥à  Sprinter.
ß°° ß°°
‚¢¥¤¥­¨¥.
„ ­­®¥ ®¯¨á ­¨¥ ¯à¥¤¯®« £ ¥â ­ «¨ç¨¥ ®¯à¥¤¥«¥­­ëå §­ ­¨© ç¨â â¥«ï,
  ¨¬¥­­® §­ ­¨¥  àå¨â¥ªâãàë ª®¬¯ìîâ¥à  ZX-Spectrum ¨ ¨å à §­®¢¨¤­®á⥩, ¢
ç áâ­®á⨠Pentagon-128 ¨ Scorpion-256,   â ª ¦¥ §­ ­¨¥ ï§ëª  BASIC ¨
­¥ª®â®à®¥ §­ ª®¬á⢮ á ï§ëª®¬  áᥬ¡«¥à  Z80.
‡¤¥áì ï ¡ã¤ã ­ §ë¢ âì ª®­ä¨£ãà æ¨¥© ¬ è¨­ë - ª®­ªà¥â­ãî ॠ«¨§ æ¨î
ª®­ªà¥â­®© áå¥¬ë ¢ ¯¥à¥¯à®£à ¬¬¨à㥬®© «®£¨ç¥áª®© ¬¨ªà®á奬¥ (‹Œ).
â® ®§­ ç ¥â, çâ® ¬ è¨­  ¨¬¥¥â ¬­®¦¥á⢮ ª®­ä¨£ãà æ¨©, ª ¦¤ ï ¨§ ª®â®àëå
¨¬¥¥â ᢮î á奬ã.
Ÿ â ª ¦¥ ¨á¯®«ì§ãî ¯®­ï⨥ Š˜-އ“. â® ­¥ Š˜ ¢ ä®à¬ «ì­®¬
á¬ëá«¥,   ¡ëáâ஥ އ“, ¢ ª®â®à®¬ ¯à®æ¥áá®à ¬®¦¥â à ¡®â âì ­  ¢ë᮪®©
ç áâ®â¥ ¡¥§ ®¦¨¤ ­¨ï. Š˜-¥¬ íâ® Ž‡“ ­ §ë¢ ¥âáï ⮫쪮 ¯® âà ¤¨æ¨¨,
¯®¤®¡­® Š˜-ã ­  Š537“10 ¢ ª®¬¯ìîâ¥à å Pentagon-128.
Šà âª¨¥ ¤ ­­ë¥ ª®¬¯ìîâ¥à  Sprinter.
p®æ¥áá®p . . . . . . . . . . . Z84C15
’ ªâ®¢ ï ç áâ®â  . . . . 21MHz/3.5MHz
އ“ . . . . . . . . . . . . . . 4096Kb
Š˜ އ“ . . . . . . . . . . . . . 64Kb
‡“ . . . . . . . . . . . . . . .128Kb
‚¨¤¥®-އ“ . . . . . . . . . 256Kb(512)
Š®­âp®««¥p ¤¨áª®¢ . . . . . Šp1818‚ƒ93
®¤¤¥p¦ª  1.44Mb ä®p¬ â  . . 3.5"¤¨áª 
Š®­âp®««¥p ¢¨­ç¥áâ¥p  . . . . . IDE/AT
Š®­âp®««¥p ª« ¢¨ âãpë . . . 101key/AT
Š®­âp®««¥p ¬ëè¨ . . . . . . . MS-Mouse
„¢  á«®â  . . . . . . . áâ ­¤ pâ ISA-8
†¥«¥§­ ï í¬ã«ïæ¨ï AY-3-8910 áâ¥à¥®-OUT
COVOX . . . . . . . . . 8bit x 4chanel
‚¨¤¥®-p¥¦¨¬ë: . . . Spectrum standart
GRAF 320x256x256,640x256x16, TXT 80x32
‚ë室 ¢¨¤¥® ­  TV ¨«¨ CGA ¬®­¨â®p, RGB
’¥å­¨ç¥áª ï ॠ«¨§ æ¨ï.
Ÿ¤à®¬ ¬ è¨­ë ïîâáï ¯à®æ¥áá®à Z84C15 ¨ ‹Œ EPF10K10QC208.
Šà®¬¥ ­¨å ­  ¯« â¥ ¯à¨áãâáâ¢ãîâ ¬¨ªà®á奬a ‡“, 72å-¯¨­®¢ë© SIMM
­  4Mb, 256Kb ¢¨¤¥®-އ“, 64Kb Š˜-އ“, á奬  ª®­â஫«¥à  ¤¨áª®¢®¤  ­ 
ˆ‘ Š1818‚ƒ93, ¡ãä¥àë ¤«ï ¯®¤ª«î祭¨ï ¤¦®©á⨪ , ¬ £­¨â®ä®­ , ¯à¨­â¥à ,
ª« ¢¨ âãàë, ¤¨áª®¢®¤®¢, ¢¨­ç¥áâ¥à , ¬ëè¨, ¡ãä¥à­ë¥ ¬¨ªà®á奬ë 設ë ISA-8
¨ ¥é¥ ®¤­  ‹Œ ä¨à¬ë ALTERA - EPM7032LC44. â  ‹Œ ­¥ ¬¥­ï¥â ᢮¥©
ª®­ä¨£ãà æ¨¨ ¨ ¯à¥¤­ §­ ç¥­  ¤«ï ®¡¥á¯¥ç¥­¨ï ᨭåà®­¨§ æ¨¨ ¨ ­ ç «ì­®£®
§ ¯ã᪠ ª®¬¯ìîâ¥à .   ¯« â¥ â ª ¦¥ ¯à¥¤ãᬮâ७  ¢®§¬®¦­®áâì ¯®¤ª«î祭¨ï
CMOS ç á®¢ ­  ®á­®¢¥ ¬¨ªà®á奬ë DALLAS. Šà®¬¥ ¯¥à¨ä¥à¨¨ ¨ ¡ãä¥à®¢ ¨¬¥îâáï
¬¨ªà®áå¥¬ë ¤¥è¨äà æ¨¨, ¢å®¤ë ª®â®àëå ¯®¤ª«îç îâáï ª ¯à®æ¥áá®àã ç¥à¥§ ‹Œ.
â® ¯®§¢®«ï¥â «¥£ª® ¬¥­ïâì  ¤à¥á æ¨î ãáâனá⢠¡¥§ ª ª®£® «¨¡® ¨§¬¥­¥­¨ï
à §¢®¤ª¨ ¯¥ç â­®© ¯« âë.
‚®§¬®¦­®á⨠ àå¨â¥ªâãàë ¬ è¨­ë.
‘奬  ª®¬¯ìîâ¥p  ®á­®¢ ­  ­  ¡®«ì让 ¯¥p¥¯p®£p ¬¬¨p㥬®© «®£¨ç¥áª®©
¬¨ªp®á奬¥. ®¤ª«î祭¨¥ ¯¥à¨ä¥à¨©­ëå ãáâனá⢠ç¥à¥§ ‹Œ ¯®§¢®«ï¥â ¯®«ãç¨âì
¢ë᮪ãî £¨¡ª®áâì ¬ è¨­ë ¯® ª®­ä¨£ãà æ¨ï¬.
p®£p ¬¬¨p®¢ ­¨¥ ‹Œ ®áãé¥á⢫ï¥âáï ­¥¯®áp¥¤á⢥­­® ¢ ¬®¬¥­â
¢ª«î祭¨ï,   â ª ¦¥ ¯p¨ ¯¥p¥§ £p㧪¥, çâ® ¯®§¢®«ï¥â ª p¤¨­ «ì­® ¬¥­ïâì
á奬㠢 ‹Œ ­¥¯®áp¥¤á⢥­­® ¢® ¢p¥¬ï p ¡®âë. ⮠ᨫ쭮 ¢ë¤¥«ï¥â
 àå¨â¥ªâãàã ª®¬¯ìîâ¥à  ¨§ à鸞 áãé¥áâ¢ãîé¨å ª®¬¯ìîâ¥à®¢ ¨ ¯®í⮬㠬­®£¨¥
¯®­ïâ¨ï, ¯à¨áã騥 ®¡ëç­ë¬ ¬ è¨­ ¬, ¬¥­ïîâ ᢮© á¬ëá«. ” ªâ¨ç¥áª¨ ª®¬¯ìîâ¥à
¨¬¥¥â ¨§¬¥­ï¥¬ãî  àå¨â¥ªâãàã, ¢ ª®â®à®© ¢®§¬®¦­ë ¨§¬¥­¥­¨ï ¢® ¬­®£¨å ç áâïå
á奬ë. ’ ª, ­ ¯à¨¬¥à, ­¥«ì§ï £®¢®à¨âì ® ª®­ªà¥â­ëå  ¤à¥á å ¯®à⮢
¯®¤ª«î祭¨ï ¯¥à¨ä¥à¨¨, â ª ª ª ®­¨ ¬®£ãâ ¡ëâì ¨§¬¥­¥­ë ¢ ®¤­ã ᥪ㭤ã
¯ã⥬ ¯¥à¥¯à®£à ¬¬¨à®¢ ­¨ï ‹Œ ¨ ¤ ­­ëå ¢ އ“, ®â¢¥ç îé¨å §  ª®­ä¨£ãà æ¨î
¯®à⮢. Š®­ªà¥â­ë¥  ¤à¥á  ¯®ï¢«ïîâáï ⮫쪮 ¢ ª®­ªà¥â­ëå ª®­ä¨£ãà æ¨ïå,
­ ¯à¨¬¥à, â ª®© ª ª ª®­ä¨£ãà æ¨ï ZX-Spectrum.
¥à¥¯à®£à ¬¬¨à㥬®áâì áå¥¬ë ¤ ¥â ¤®¢®«ì­® ¡®«ìèãî ᢮¡®¤ã
ä ­â §¨¨ ¯à®£à ¬¬¨áâ  ¯® ª®­ä¨£ãà æ¨¨ ¬ è¨­ë. ‡ ¤ã¬ë¢ ï ª®­ªà¥â­ãî
à ¡®â㠯ணࠬ¬¨áâ ¬®¦¥â ®¯à¥¤¥«¨âì ¢ ª ª®© ª®­ä¨£ãà æ¨¨ ¥¥ ¬®¦­®
ᤥ« âì «ãçè¥,  , ¢®§¬®¦­®, ¨ ¯à¨¤ã¬ âì á¢®î ª®­ä¨£ãà æ¨î, ª®â®àãî
§ â¥¬ ¬®¦­® ॠ«¨§®¢ âì ¢ ‹Œ ¨ ¢ª«îç¨âì ¯¥à¥¤ § ¯ã᪮¬ í⮩ ¯à®£à ¬¬ë.
«®ç­ ï á奬  ª®¬¯ìîâ¥à  Sprinter.
ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄ>Sinc
³à¨­â¥à³ ³ 2 ISA SLOTS ³ ³ ÚÄÄÄÄÄÄÄ>R
ÃÄÄÄÄÄÄÄ´ ÀÄÄÄÂÄÂÄÂÄÂÄÂÄÄÄÄÄÄÄÂÄÂÄÂÄÂÄÂÄÄÙ ³ ³ ÚÄÄÄÄÄ>G
³ Œëèì ³ ÚÄÄÄÁÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄÁÄÁÄÁÄÁÄÁÄÄ¿ ³ ³ ³ ÚÄÄÄ>B
ÀÄÂÄÂÄÂÄÙ ³ ãä¥àë ¨ ¤¥è¨äà â®àë ³ ³ ÚÄÁÄÁÄÁÄ¿
³ ³ ³ ÀÄÄÄÂÄÄÄÄÄÄÄÄÄÄÂÄÂÄÄÂÄÂÄÄÄÄÂÄÂÄÙ ³ ³ –€ ³
³ ³ ³ ³INT ³ ³ ³ ³ ³ ³ ³ ³ ¡ãä¥à ³
ÚÄÄÁÄÁÄÁÂÄ¿ ³ ³ ³ ³ ³ ÚÁÄÁÄÄÄÄÄÄÁÄ¿ÀÄÄÂÄÂÄÄÙ ÚÄÄÄÄÄÄÄÄ¿
³ ‚­ãâà ³ ÃÄÄÄijÄÄÄÄÄÄÄÄÄÄÙ ÀÄij ³ÄÄÄ´ EPF10K10 ÃÄÄÄÙ ÀÄÄÄÄÄÄ´ ¢¨¤¥® ³
³ ¯®àâë ³ ÃÄÄÄijÄÄÄÄÄÄÄÄÄÄDATAij ³ÄÄÄ´ ÃÄÄV_DATAÄÄÄÄ´ ³
ÃÄÄÄÄÄÄÄÙ ÃÄÄÄijÄÄÄ¿ ÚÄÄÄÄÄÄÄÄij ³ÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÄ´ އ“ ³
³ Ã<ÄÄÄÙ ³ ³ ³ ³ ³ ³ ³ ³
³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÄ´ ³
³ Z84C15 ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄADRESSÄÄÄÄÄ´ ÃÄV_ADRESSÄÄÄ´ ³
³ ÃÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÄ´ ³
³ ³ ³ ³ ³ ³ ³ ³ ³ ³
³ Ã<ÄÄij ³Ä³ ³ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>´ ÃÄÄWE,CSiÄÄÄ>´ ³
³ Ã<ÄÄij ³Ä³ ³ÄÄÄÄDIRÄÄÄÄÄÄÄ>´ ³ ÀÄÄÄÄÄÄÄÄÙ
³ Ã<ÄÄij ³Ä³ ³ÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>´ ÃÄÄÄÄÄÄÄ> Audio OUTs
³ ³ ³ ³ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄ¿
³ ³ ÚÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄ´ MAIN ³
³ ³ ³ ‡“ ¨ Š˜-އ“ Ã<ÄADR'Ä´ ÃÄÄDATAÄÄÄÄÄÄ´ RAM ³
ÀÄÂÄÂÄÂÄÄÂÙ ³ CS Ã<ÄCSÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÄ´ ³
³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ SIMM ³
³ D ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄ´ ³
³ A ³ ³ ³ EPM7032 ÃÄÄÄÄÄÄÄ>´ ÃÄÄADRESSÄÄÄÄ´ ³
³ T ³ ÀÄÄ>´ Sinchro ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄ´ ³
³ A ³ ³ HDD_DIR Ã<ÄÄÄÄÄÄÄ´ ³ ³ ³
³ ³ ³ ³ ”€— Ã<ÄÄÄÄÄÄÄ´ ÃÄRAS,CAS,WEÄ´ ³
³ ³ ³ ÀÄÂÄÂÄÂÄÂÄÂÄÄÄÄÙ ³ ³ ÀÄÄÄÄÄÄÄÄÙ
³ ³ ³ ³ ³ ³ ³ ³ ³ ³
ÚÄÁÄÁÄÁÄÄÄÄÄÄÄÄÁÄÁÄÁÄÁÄÁÄÄÄÄÄÄÄ¿ ³ ³
³ ¥à¨ä¥à¨©­ë¥ ãáâனá⢠ Ã<ÄÄÄÄ´ ³
³ FDD,HDD,KEMPSTON Ã<DIRÄ´ ³
³ Ã<ÄÄÄÄ´ ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>´ ³
ÚÄÄÄÄÄÄÄÄ¿ ³ ÚÄÄÄÄÄÄÄÄ¿ ³ ³
³ TAPE Ã<ÄÄÙ ³KeyboardÃÄÄÄÄÄÄÄÄÄ>´ ³
³ in/out ³ ô ³ ³
ÀÄÄÄÄÄÄÄÄÙ ÀÁÁÁÁÁÁÁÁÙ ÀÄÄÄÄÄÄÄÄÄÄÄÙ
¨áã­®ª 1.
„«ï ¯à®áâ®âë ­¥ª®â®àë¥ ¡ãä¥àë ¨ ¤¥è¨äà â®àë ­  á奬¥ ­¥ 㪠§ ­ë.
Š®«¨ç¥á⢮ ஢®¤®¢ ¢ 設 å â ª ¦¥ ãá«®¢­ë. — áâì ᨣ­ «®¢ ã¯à ¢«¥­¨ï
ãáâனáâ¢ á ‹Œ á¬ã«ì⨯«¨æ¨à®¢ ­ë á  ¤à¥á ¬¨ SIMM- .
„ «ì­¥©è¥¥ ®¯¨á ­¨¥  àå¨â¥ªâãàë ï¥âáï ®¯¨á ­¨¥¬ ª®­ªà¥â­ëå
ª®­ä¨£ãà æ¨© ¨ ç á⥩ ª®­ä¨£ãà æ¨©. ® ¯¥à¥¤ í⨬ á«¥¤ã¥â ᪠§ âì ­¥áª®«ìª®
á«®¢ ® ¯¥à¥ª«î祭¨¨ á ¬¨å ª®­ä¨£ãà æ¨©.
‡ £à㧪  ª®­ä¨£ãà æ¨©.
‚ ¬®¬¥­â ¢ª«î祭¨ï ª®¬¯ìîâ¥à ,   â ª ¦¥ ¯®á«¥ ­ ¦ â¨ï ­  RESET ¢áï
¨­ä®à¬ æ¨ï, ­ å®¤¨¢è ïáï ¢ ‹Œ ®â¢¥ç îé ï §  ª®­ªà¥â­ãî ª®­ä¨£ãà æ¨î,
áâ¨à ¥âáï. ‹Œ ¯¥à¥å®¤¨â ¢ ०¨¬ ®¦¨¤ ­¨ï § £à㧪¨ ¡«®ª  ¤ ­­ëå á奬ë.
‚ íâ®â ¬®¬¥­â ¯à®æ¥áá®à ¯®«­®áâìî ®âª«î祭 ®â ª ª®© «¨¡® ¯¥à¨ä¥à¨¨.
‚ ¥£®  ¤à¥á­®¥ ¯à®áâà ­á⢮ ¯ ¬ï⨠®ª §ë¢ ¥âáï ¢ª«î祭  ®¤­  áâà ­¨æ  ‡“ ¨
®¤­  áâà ­¨æ  އ“ Š˜-¯ ¬ïâ¨. ‹î¡ ï § ¯¨áì ¢  ¤à¥á­®¥ ¯à®áâà ­á⢮ ¯ ¬ïâ¨
¯à®æ¥áá®à  ¢ íâ®â ¬®¬¥­â ¯à¨¢®¤¨â ª § ¯¨á¨ ¤ ­­ëå ¢ ‹Œ ¨ ¯à®£à ¬¬  ¢
¯®¤ª«î祭­®© áâà ­¨æ¥ ‡“ ¨¬¥¥â ⮫쪮 ®¤­ã ¥¤¨­á⢥­­ãî æ¥«ì - § £à㧨âì
¢ ‹Œ ¤ ­­ë¥ ª®­ä¨£ãà æ¨¨. ‚ í⮩ ¦¥ áâà ­¨æ¥ ‡“ ­ å®¤ïâáï ¤ ­­ë¥
­ ç «ì­®© ª®­ä¨£ãà æ¨¨. (‚ ¤ ­­ë© ¬®¬¥­â íâ® ª®­ä¨£ãà æ¨ï Sprinter-1.)
ணࠬ¬  § £à㧪¨ ª®­ä¨£ãà æ¨¨ ¯à®¢¥àï¥â ä« £ ¢ Š˜-¯ ¬ï⨠¨, ¥á«¨ ®­
ãáâ ­®¢«¥­, § £à㦠¥â ¢ ‹Œ ¤ ­­ë¥ ¨§ އ“, ¥á«¨ á¡à®è¥­, â® ¤ ­­ë¥ ¨§
‡“.   í⮬ ®á­®¢ ­® ¯¥à¥ª®­ä¨£ãà¨à®¢ ­¨¥ áå¥¬ë ª®¬¯ìîâ¥à .
„«ï ¨§¬¥­¥­¨ï áå¥¬ë ­ ¤® § £à㧨âì ¢ ¯®á«¥¤­îî áâà ­¨æã Š˜-¯ ¬ïâ¨
¡«®ª ¤ ­­ëå ª®­ä¨£ãà æ¨¨ ᮠᬥ饭¨ï #100 ¨ ¢ëáâ ¢¨âì ä« £, ª®â®àë¬ ï¢«ï¥âáï
⥪á⮢ ï áâப  "FLEX_10K_LOADING", § ¯¨á ­­ ï ¯® ᬥ饭¨î #80 ¢ í⮩ ¦¥
áâà ­¨æ¥ Š˜- . ®á«¥ í⮣® ­ ¤® ¯à®¨§¢¥á⨠¯®«­ë© á¡à®á, ª®â®àë©
®áãé¥á⢫ï¥âáï ¯à®£à ¬¬­® § ¯¨áìî ¢ ᯥ樠«ì­ãî áâà ­¨æã ¯ ¬ï⨠RESET_PAGE.
ணࠬ¬  ¢ ‡“, § ¯ã᪠¥¬ ï ¯® á¡à®áã ­ å®¤¨â ä« £ FLEX_10K_LOADING ¨
­ ç¨­ ¥â § £à㧪㠤 ­­ëå ¢ ‹Œ. ਠí⮬ ®­  ®¤­®¢à¥¬¥­­® § â¨à ¥â ä« £,
çâ® ¯à¥¤®â¢à é ¥â ¯®¢â®à­ãî § £à㧪㠭®¢®© ª®­ä¨£ãà æ¨¨ ¯à¨ ­ ¦ â¨¨ ­ 
ª­®¯ªã RESET ¨ ¯®§¢®«ï¥â ¢¥à­ãâìáï ¯®á«¥ "àãç­®£®" á¡à®á  ¢ ­ ç «ì­ãî
ª®­ä¨£ãà æ¨î. ‡ â¨à ­¨¥ ä« £  â ª ¦¥ ¨§¡ ¢«ï¥â ®â ¬ã祭¨© ¢ á«ãç ¥
¯®¤ª«î祭¨ï ­¥¯à ¢¨«ì­®© ª®­ä¨£ãà æ¨¨ ¢® ¢à¥¬ï íªá¯¥à¨¬¥­â®¢ á ¯à®£à ¬¬ ¬¨.
 ¦ â¨¥ ­  RESET ¢á¥£¤  ¢¥à­¥â á奬㠢 ­ ç «ì­ãî ª®­ä¨£ãà æ¨î.
ਬ¥ç ­¨¥:
‚­ãâ७­ïï ¨­ä®à¬ æ¨ï ¡«®ª  ¤ ­­ëå ‹Œ ï¥âáï § ªàë⮩
¨­ä®à¬ æ¨¥© ä¨à¬ë ALTERA. Šà®¬¥ á ¬¨å ¬¨ªà®á奬 ‹Œ ALTERA ¯®áâ ¢«ï¥â
¨ ¯à®£à ¬¬­®¥ ®¡¥á¯¥ç¥­¨¥ ¤«ï à §¢®¤ª¨ á奬 ¢­ãâਠ‹Œ. Š á®¦ «¥­¨î, íâ 
¯à®£à ¬¬  ­¥ ¬®¦¥â à ¡®â âì ­  ª®¬¯ìîâ¥à¥ ⨯  ZX-Spectrum ¨ ¢ ¡«¨¦ ©è¥¬
®¡®§à¨¬®¬ ¡ã¤ã饬 ­¥ ¯à¥¤¢¨¤¨âáï ¥¥ ¢¥àá¨ï ¤«ï Sprinter- . ®í⮬ã
à §à ¡®âª  ­®¢ëå ª®­ä¨£ãà æ¨© ¬®¦¥â ¯à®¨§¢®¤¨âáï ⮫쪮 ¯à¨ ­ «¨ç¨¨
¤®áâ â®ç­® ¬®é­®© ¬ è¨­ë (¢á¥ ¤¥« «®áì ­  Pentium-166) ¨ ¯à®£à ¬¬ë à §¢®¤ª¨
á奬 ¢ ‹Œ, 業  ­  ª®â®àãî á®áâ ¢«ï¥â á®â­¨ ¤®«« à®¢ ‘˜€.
‚ á¢ï§¨ á í⨬, ¢ ¤ ­­ë© ¬®¬¥­â Sprinter ¨¬¥¥â ­¥áª®«ìª® ª®­ªà¥â­ëå
ª®­ä¨£ãà æ¨©, ¤¢¥ ¨§ ª®â®àëå § ¯¨á ­ë ¢ ‡“,   ®áâ «ì­ë¥ ¬®£ãâ ¡ëâì
¯®¤£à㦥­ë á ¤¨áª¥âë ¨«¨ ¢¨­ç¥áâ¥à . ®áâ®ï­­® ¢¥¤¥âáï ᮢ¥à襭á⢮¢ ­¨¥
ª®­ªà¥â­ëå ª®­ä¨£ãà æ¨© ¨ à §à ¡®âª  ­®¢ëå.
Š®­ä¨£ãà æ¨ï Sprinter-1.
‚ª«î砥⠢ á¥¡ï ª®­ä¨£ãà æ¨î Spectrum-128/256, à á¯à¥¤¥«¥­¨¥ ¯ ¬ïâ¨
¤® 4Mb, à áè¨à¥­­ë© íªà ­ á ०¨¬ ¬¨ Spectrum, Text-80x32, Graf-320x256x256,
ª®­â஫«¥à ¤¨áª®¢®¤ , ª®­â஫«¥à IDE ¢¨­ç¥áâ¥à , ª®­â஫«¥à ª« ¢¨ âãàë AT,
¯®¤ª«î祭­®© ª ª ZX-Keyboard, 8-bit COVOX.
â  ª®­ä¨£ãà æ¨ï ¬ ªá¨¬ «ì­® ¯à¨¡«¨¦¥­  ª ª®­ä®£ãà æ¨¨ ZX-Spectrum
¨ ¯®§¢®«ï¥â à ¡®â âì ­  ®¡ëç­ëå ᯥªâà㬮¢áª¨å ¯à®£à ¬¬ å ¨ ¯®á⥯¥­­®
¬¥­ïâì ¨å ¯®¤ à áè¨à¥­­ë¥ ०¨¬ë íªà ­  ¨ ¯ ¬ïâ¨,   â ª ¦¥ ¤«ï à ¡®âë á
­®¢ë¬¨ ãáâனá⢠¬¨.
Š®­ä¨£ãà æ¨ï Sprinter-2.
‚ª«î砥⠢ á¥¡ï ª®­ä¨£ãà æ¨î Spectrum-128/256, à á¯à¥¤¥«¥­¨¥ ¯ ¬ïâ¨
¤® 4Mb, à áè¨à¥­­ë© íªà ­ á ०¨¬ ¬¨ Spectrum, Text-80x32, Graf-320x256x256,
ª®­â஫«¥à ¤¨áª®¢®¤ , ª®­â஫«¥à IDE ¢¨­ç¥áâ¥à , ª®­â஫«¥à ª« ¢¨ âãàë AT,
¯®¤ª«î祭­®© ª ª ZX-Keyboard, Accelerator.
Š®­ä¨£ãà æ¨ï, ª ª ¨ Sprinter-1 ¯à¨¡«¨¦¥­  ª ᯥªâà㬮¢áª®©, ­®
¨¬¥¥â ¡®«¥¥ ¦¥á⪨¥ âॡ®¢ ­¨ï ª ¯à®£à ¬¬ ¬ ¯® ᮢ¬¥á⨬®áâ¨. ®§¢®«ï¥â
¨á¯®«ì§®¢ âì  ªá¥«¥à â®à ®¯¥à æ¨© á ®á­®¢­ë¬ ¨ ¢¨¤¥®-އ“. €ªá¥«¥à â®à
ã᪮àï¥â ®¯¥à æ¨¨ ¯¥à¥á뫪¨ ¡«®ª®¢ ¤ ­­ëå ¨ § ¯®«­¥­¨ï އ“ ®¤­¨¬ ¡ ©â®¬
¤® 䨧¨ç¥áª®£® ¯à¥¤¥«  ᪮à®á⨠®á­®¢­®£® އ“.
‚ ¯®á«¥¤­¥© ¢¥àᨨ ª®­ä¨£ãà æ¨ï Sprinter-2 ­¥ ¨¬¥¥â Spectrum-®¢áª®©
ª« ¢¨ âãàë. ‚¬¥áâ® ­¥¥ ¨§ ¯®àâ  0FEh áç¨â뢠¥âáï ᪠­ª®¤ ¯à¨è¥¤è¨© á
AT-ª« ¢¨ âãàë.
Š®­ä¨£ãà æ¨ï ZX-Spectrum-256/AY.
â  ª®­ä¨£ãà æ¨ï ¬ ªá¨¬ «ì­® ¯à¨¡«¨¦¥­  ª ZX-Spectrum-128/256
¨ ¢ª«î砥⠢ ᥡï á奬㠬ã§ëª «ì­®£® á®¯à®æ¥áá®à  AY-3-8910. ‚ í⮩
ª®­ä¨£ãà æ¨¨ ®âáãâáâ¢ãîâ à áè¨à¥­­ë¥ ०¨¬ë íªà ­ .
‚â®à ï ¢¥àá¨ï á奬ë AY ¢ª«î砥⠢ ᥡï âਠ£¥­¥à â®à  £®«®á®¢,
£¥­¥à â®à è㬠 ¨ ॣã«ïâ®àë  ¬¯«¨âã¤ë. ƒ¥­¥à â®à ®£¨¡ î饩 ®âáãâáâ¢ã¥â.
’ ª ¦¥ ®âáãâáâ¢ã¥â ¢®§¬®¦­®áâì ç⥭¨ï ¨§ ¯®à⮢ ¤ ­­ëå á®¯à®æ¥áá®à .
‚ âà¥â쥩 ¢¥àᨨ AY ¯à¥¤¯®« £ ¥âáï ¤ ­­ë¥ ­¥¤®áâ âª¨ ¨áª«îç¨âì.
Š®­ä¨£ãà æ¨ï Sprinter-3.
Š®­ä¨£ãà æ¨ï ®â¢ï§ ­  ®â ª®­ä¨£ãà æ¨¨ ZX-Spectrum. ®«­®áâìî
®âª«îç ¥âáï ‡“ ¨ ¢á¥  ¤à¥á­®¥ ¯à®áâà ­á⢮ à §¡¨â® ­  ç¥âëॠ®ª­  ¯® 16k,
¢ ª ¦¤®¥ ¨§ ª®â®àëå ¯®¤ª«îç ¥âáï «î¡ ï ¨§ 256-⨠áâà ­¨æ އ“. Žâáãâáâ¢ã¥â
ᯥªâà㬮¢áª¨© íªà ­, £à ä¨ç¥áª¨© íªà ­ â ª®© ¦¥, ª ª ¢ ª®­ä¨£ãà æ¨ïå
Sprinter-1 ¨ Sprinter-2. ˆ¬¥¥â ¤®¯®«­¨â¥«ì­ë¥ ä㭪樨  ªá¥«¥à â®à .
®§¢®«ï¥â ¯à®¨§¢®¤¨âì ®¯¥à æ¨¨ AND, OR ¨ XOR á ¡«®ª ¬¨ ¤ ­­ëå. ˆ¬¥¥â 8-bit
COVOX.
‚ ¤ «ì­¥©è¥¬ ¯à¥¤¯®« £ ¥âáï ¯®¤ª«î祭¨¥ ¢ í⮩ ª®­ä¨£ãà æ¨¨
á¯à¨­â¥à®¢áª®© §¢ãª®¢®© ª àâë.
Š®­ä¨£ãà æ¨ï Game-1.
®å®¦  ­  ª®­ä¨£ãà æ¨î Sprinter-3. €ªá¥«¥à â®à ­¥ ¨¬¥¥â «®£¨ç¥áª¨å
ä㭪権,   ¤«ï ¢ë¢®¤  §¢ãª  ¨¬¥¥â COVOX-Blaster - COVOX á ¡ãä¥à­ë¬ އ“,
¯®§¢®«ïî騬 ¢ë¢®¤¨âì §¢ãª ¯®¡«®ç­® ¨ ®á¢®¡®¦¤ âì ¯à®æ¥áá®à­®¥ ¢à¥¬ï ¤«ï
¤à㣮© à ¡®âë. Š®­ä¨£ãà æ¨ï ®à¨¥­â¨à®¢ ­  ­  ¨á¯®«ì§®¢ ­¨¥ ¢ ¨£à å ¤«ï
Sprinter- .
‘奬  à á¯à¥¤¥«¥­¨ï ¯ ¬ïâ¨.
‘奬ë à á¯à¥¤¥«¥­¨ï ¯ ¬ï⨠¢ ª®­ä¨£ãà æ¨ïå Sprinter-1 ¨ Sprinter-2
®¤¨­ ª®¢ë ¨ ¤®áâ â®ç­® ¯à®§à ç­ë. ” ªâ¨ç¥áª¨ ®­  ¯à¥¤áâ ¢«ï¥â ᮡ®© á奬ã
à á¯à¥¤¥«¥­¨ï ¯ ¬ï⨠ª®¬¯ìîâ¥à  Scorpion, á ­ «®¦¥­­®© ­  ­¥¥
¤®¯®«­¨â¥«ì­®© á奬®©, ª®â®à ï ¯®§¢®«ï¥â ¯à®¨§¢®«ì­® ãáâ ­ ¢«¨¢ âì ¢á¥
áâà ­¨æë ¯ ¬ïâ¨, ª ª ‡“, â ª ¨ އ“.
Š ¦¤ ï áâà ­¨æ  ‡“ ¨«¨ އ“ ¨¬¥¥â ᢮© ¯®àâ, ¢ ª®â®à®¬ 㪠§ë¢ ¥âáï
¤¥©á⢨⥫ì­ë© ­®¬¥à áâà ­¨æë ¨§ 256-⨠áâà ­¨æ ¢á¥å 4Mb. ‘âà ­¨æë,
¯à®¥æ¨àã¥¬ë¥ ¢ à §«¨ç­ë¥ ®ª­   ¤à¥á­®£® ¯à®áâà ­á⢠ ¯à®æ¥áá®à  ¨¬¥îâ ᢮¨
ᮡá⢥­­ë¥ ¯®àâë. ’.¥. ‘âà ­¨æ , ¢ª«îç ¥¬ ï ¢  ¤à¥á  #4000..#7FFF, ¨
áâà ­¨æ  ­®¬¥à 5 ®¡ëç­®£® Spectrum-®¢áª®£® à á¯à¥¤¥«¥­¨ï ¯ ¬ïâ¨, ¢ª«îç ¥¬ ï
¢  ¤à¥á  #C000..#FFFF ¨¬¥îâ à §¤¥«ì­ë¥ ¯®àâë.
‚ᥣ® â ª¨å ¯®à⮢ áâà ­¨æ ¯ ¬ï⨠- 32.
16 ¯®à⮢ ®â¢¥ç îâ §  ­®¬¥à  áâà ­¨æ އ“, ¯®¤ª«îç ¥¬ë¥ ª  ¤à¥á ¬
#C000..#FFFF. …é¥ âਠ¯®àâ  ®â¢¥ç îâ §  ¯®¤ª«î祭¨¥ áâà ­¨æ އ“ ª  ¤à¥á ¬
#0000..#3FFF, #4000..#7FFF ¨ #8000..#BFFF. ‚®á¥¬ì ¯®à⮢ ¨á¯®«ì§ãîâáï ¤«ï
¯®¤ª«î祭¨ï à §«¨ç­ëå áâà ­¨æ ‡“. ޤ¨­ ¯®àâ - ¤«ï ¯®¤ª«î祭¨ï áâà ­¨æë
Š˜-  ¢¬¥áâ® ‡“. ˆ ®¤¨­ ¯®àâ - íâ® ¯®àâ á¨á⥬­®£® ‡“, ¯®¤ª«îç ¥¬®£® ­ 
¬¥áâ® ‡“ áà §ã ¯®á«¥ á¡à®á  ¬ è¨­ë ¯® ª« ¢¨è ¬ Ctrl+Alt+Del.
Žá⠢訥áï 3 ¯®àâ  áâà ­¨æ ¯ ¬ï⨠®áâ îâáï ­  ¤ ­­ë© ¬®¬¥­â ¢ १¥à¢¥.
‘奬  à á¯à¥¤¥«¥­¨ï ¯ ¬ï⨠¯®§¢®«ï¥â ¯®¤ª«îç¨âì ¢  ¤à¥á­®¥ ¯à®áâà ­á⢮
¯à®æ¥áá®à  ­¥ ⮫쪮 އ“ ¨«¨ ‡“, ­® ¨ ¯®àâë ¨ ¯ ¬ïâì ISA ª àâ, ¢áâ ¢«ï¥¬ëå
¢ á«®â.
ਠ¯®¤ª«î祭¨¨ ¢  ¤à¥á  #C000..#FFFF ᪮௨®­®¢áª¨å à áè¨à¥­­ëå
áâà ­¨æ އ“, ­  ¨å ¬¥áâ® ¬®¦­® ¯¥à¥ ¤à¥á®¢ âì á«®âë. „«ï í⮣® ­ ¤® ¯à®áâ®
§ ¯¨á âì ¢ ¯®àâ ®¤­®© ¨§ íâ¨å áâà ­¨æ §­ ç¥­¨¥, ᮮ⢥âáâ¢ãî饥 ISA-á«®âã,
ª ª®â®à®¬ã ­¥®¡å®¤¨¬® ¯à®¨§¢¥á⨠®¡à é¥­¨¥. â® §­ ç¥­¨¥ â ª ¦¥ 㪠§ë¢ ¥â
ª 祬㠢¥¤¥âáï ®¡à é¥­¨¥, ª ¯®àâ ¬ ¨«¨ ¯ ¬ïâ¨.
‘奬  à á¯à¥¤¥«¥­¨ï ¯ ¬ï⨠Sprinter- .
€¤à¥á­®¥ ¯à®áâà ­á⢮ ‘âà ­¨ç­ë¥ ¯®àâë ‡“ ª®¬¯ìîâ¥à 
¯à®æ¥áá®à  ¯®¤ª«îç ¥¬ëå áâà ­¨æ áâà ­¨æë ¯® 16k
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ #0000..#3FFF ÃÄ>´ ¯®àâë ÃÄÄ>´ ROM_BASIC ÃÄÄ>ÂÄÄÂÄÄÂÄ>´ EXPANSION ³
³ ³ ³ #7FFD,³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ ³ ³ #1FFD ÃÄ¿ ³ ROM_TR-DOS ÃÄÄ>´ ³ ÃÄ>´ TR-DOS ³
ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÀÄÄÄÄÄÄÄÙ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ #4000..#7FFF ÃÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ÃÄ>´ BASIC128 ³
³ ³ ³ ³ ÃÄ Ä Ä Ä Ä Ä Ä ´ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ ³ ³ ³ ³ ROM_SYSTEM ÃÄÄ>Ù ³ ÃÄ>´ BASIC48 ³
ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ #8000..#BFFF ÃÄÄÄÄ¿ ³ À>´ RAM_0000 ÃÄÄÄ>¿ ³ ÃÄ>´ SYSTEM ROM ³
³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÃÄ>´ SYSTEM ROM2 ³
ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÀÄÄ>´ RAM_4000 ÃÄÄÄ>´ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ #C000..#FFFF ÿ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ÃÄ>´ CONFIG 2 ³
³ ³³ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ ³³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÀÄ>´ CONFIG 1 ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ³ ÀÄÄÄÄÄÄÄÄ>´ RAM_8000 ÃÄÄÄ>´ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³
³ ³ ³ އ“ ª®¬¯ìîâ¥à 
³ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ áâà ­¨æë ¯® 16k
³ ³ ®àâë ÃÄÄ>´ RAM_0 ÃÄÄÄ>´ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ³ #7FFD,³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄ>ÂÄ>´ RAM_00 ³
À>´ #1FFD ÃÄÄ>´ ÃÄÄÄ>´ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ ³ ÃÄ Ä Ä Ä Ä Ä Ä ³ ³ ³ ÃÄ>´ RAM_01 ³
³ ÃÄÄ>´ RAM_7 ÃÄÄÄ>´ ÀÄ>´ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ³
³ ÃÄÄ>´ RAM_8 ÃÄ>ÂijÄÄÄ>´ ÃÄ Ä Ä Ä Ä Ä Ä ´
³ ³ ÃÄ Ä Ä Ä Ä Ä Ä ´ ³ ³ ÀÄÄ´ RAM_7F ³
³ ÃÄÄ>´ ÃÄ>´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÀÄÄÄ>ÂÄ>´ RAM_80 ³
³ ÃÄÄ>´ RAM_F ÃÄ>ÁÄ>¿ ³ ÃÄ Ä Ä Ä Ä Ä Ä ´
ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³
‚­¥è­¨¥ ãáâனá⢠ ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ÃÄ>´ RAM_FE ³
®àâë ÃÄÄÄÄ>´ ISA_1 Ã<ÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´
ª®¬¯ìîâ¥à  ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÀÄ>´ RAM_FF ³
ÃÄÄÄÄ>´ ISA_2 Ã<ÄÄÄÄ´ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³
ÃÄÄÄÄ>´ HDD Ã<ÄÄÄÄ´
³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³
ÃÄÄÄÄ>´ OVER DEVICES Ã<ÄÄÄÄÙ
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
¨áã­®ª 2.
‚ ¤àã£¨å ª®­ä¨£ãà æ¨ïå á奬  à á¯à¥¤¥«¥­¨ï ¯ ¬ï⨠ã¯à®é ¥âáï ¤«ï
®á¢®¡®¦¤¥­¨ï à¥áãàᮢ ‹Œ. Œ®£ãâ ®âáãâá⢮¢ âì ¯®àâë #1FFD ¨ #7FFD,   â ª
¦¥ ã¯à®é ¥âáï á奬  à ¡®âë á ãáâனá⢠¬¨, ®â®¡à ¦ ¥¬ë¬¨ ­  ¯ ¬ïâì.
‘奬  à á¯à¥¤¥«¥­¨ï ¯®à⮢.
Sprinter ¨¬¥¥â ¤¢¥ ®¡®á®¡«¥­­ë¥ £àã¯¯ë ¯®à⮢. ¥à¢ ï £à㯯 , íâ®
¢­ãâ७­¨¥ ¯®àâë ¯à®æ¥áá®à  Z84C15, ¢â®à ï - ¢­¥è­¨¥ ¯®àâë. €¤à¥á æ¨ï
¯®à⮢ ¯¥à¢®© £àã¯¯ë ­¥ ¬®¦¥â ¡ëâì ¨§¬¥­¥­ , â ª ª ª í⨠¯®àâë ­  ®¤­®¬
ªà¨áâ ««¥ á ¯à®æ¥áá®à®¬. ‚â®à ï £à㯯  ¯®£ª«îç ¥âáï ç¥à¥§ ‹Œ ¨ ¨å  ¤à¥á 
¬®£ãâ ¨§¬¥­ïâìáï ª ª 㣮¤­®, á ¥¤¨­á⢥­­ë¬ ãá«®¢¨¥¬, ­¥¯¥à¥á¥ç¥­¨ï á
 ¤à¥á ¬¨ ¯¥à¢®© £à㯯ë.
Ž â®¬ ª ª¨¥ ¯®àâë ¨¬¥îâáï ­  ªà¨áâ ««¥ Z84C15 ¬®¦­® ¯à®ç¨â âì ¢
¤®ªã¬¥­â æ¨¨ ¯® íâ®¬ã ¯à®æ¥áá®àã ¨ §¤¥áì ï 㯮¬ï­ã ­¥ª®â®àë¥ ¨§ ­¨å. ޤ¨­
¨§ ¯®á«¥¤®¢ â¥«ì­ëå ¯®à⮢ ¨á¯®«ì§ã¥âáï ¤«ï ¢¢®¤  ¤ ­­ëå á  ªâ¨¢­®© ¬ëè¨.
ޤ¨­ ¨§ ¯ à ««¥«ì­ëå ¨á¯®«ì§ã¥âáï ¤«ï ¢ë¢®¤  ¤ ­­ëå, ­  ¢â®à®© ¯ à ««¥«ì­ë©
¯®àâ § ¢¥¤¥­ë ᨣ­ «ë ¯à¥à뢠­¨© ¨ § ¯à®á®¢ ¯àאַ£® ¤®áâ㯠 ᮠ᫮⮢ ISA.
 à ««¥«ì­ë© ¯®àâ ¯à®æ¥áá®à  Z84C15 ãáâ஥­ â ª¨¬ ®¡à §®¬, çâ® ­  ­¥¬
¢®§¬®¦­  ®à£ ­¨§ æ¨ï ¯à¥à뢠­¨© ¯® ᨣ­ « ¬ ¯à¨å®¤ï騬 ç¥à¥§ ¯ à ««¥«ì­ë©
¯®àâ. ” ªâ¨ç¥áª¨ ¢â®à®© ¯ à ««¥«ì­ë© ¯®à⠨ᯮ«ì§ã¥âáï ª ª ª®­â஫«¥à
¯à¥à뢠­¨©.
‘奬  à á¯à¥¤¥«¥­¨ï ¯®à⮢ ¢â®à®© £àã¯¯ë ¨¬¥¥â á¢®î ®á®¡¥­­®áâì.
ƒ« ¢­®© ¨¤¥¥© ¡ë«® ¯®«ã祭¨¥ ¢®§¬®¦­®á⨠¡ëáà® ¨§¬¥­ïâì ª®­ä¨£ãà æ¨î ¯®à⮢
¡¥§ ¯¥à¥£à㧪¨ ‹Œ. â® ¤®á⨣­ãâ® ¯ã⥬ ¯à¨¬¥­¥­¨ï ª àâë à á¯à¥¤¥«¥­¨ï
¯®à⮢, à á¯®« £ î饩áï ­  ᯥ樠«ì­®© áâà ­¨æ¥ އ“.
ਠ¯®ï¢«¥­¨¨ 横«  ®¡à é¥­¨ï ª ¯®àâã á­ ç «  ¯à®¨á室¨â ®¡à é¥­¨¥
ª އ“ ª àâë ¯®à⮢. ‚ ª à⥠¯®à⮢ § ¯¨á ­® ª ª®© ¨¬¥­­® ¯®àâ ¯®¤ª«î祭 ª
¤ ­­®¬ã  ¤à¥áã. „ «¥¥ ¯à®¨á室¨â ¢­ãâ७­ïï ¤¥è¨äà æ¨ï ¯® ¡ ©âã ¨§ ª àâë
¯®à⮢ ¨ ®¡à é¥­¨¥ ª ¢ë¡à ­­®¬ã ¯®àâã. ‚ ०¨¬¥ ­¥âãà¡® íâ® ¯à®¨á室¨â ¡¥§
ª ª¨å «¨¡® § ¤¥à¦¥ª,   ¢ ०¨¬¥ âãà¡® ¯à®æ¥áá®àã ¢ëáâ ¢«ï¥âáï ᨣ­ « WAIT ¢
§ ¢¨á¨¬®á⨠®â ­¥®¡å®¤¨¬®© ¤«¨­ë 横«  ®¡à é¥­¨ï ª ¯®àâã.
„«ï ¯®¤ª«î祭¨ï ª ª ª®¬ã «¨¡®  ¤à¥áã ¨«¨ ®âª«î祭¨ï ®â ­¥£® ª ª®£®
«¨¡® ¯®àâ  ¤®áâ â®ç­® ®âªàëâì ª àâã ¯®à⮢ ¨ ¢¯¨á âì ¢ ­ã¦­®¥ ¬¥áâ® ®¤¨­
¡ ©â.
‚ áâà ­¨æ¥ ª àâë ¯®à⮢ ᮤ¥à¦¨âáï ç¥âëॠª àâë, ª®â®àë¥ ¬®£ãâ
¯¥à¥ª«îç âìáï ç¥à¥§ á¨á⥬­ë© ¯®àâ. ’ ª¨¬ ®¡à §®¬ ¬®¦­® ®áãé¥á⢨âì ¡ëáâ஥
¯¥à¥ª«î祭¨¥ ª®­ä¨£ãà æ¨¨ ¯®à⮢, çâ® ¬®¦¥â ¡ëâì ¯®«¥§­® ¯à¨ à ¡®â¥
Spectrum-®¢áª¨å ¯à®£à ¬¬ ᮢ¬¥áâ­® á® Sprinter-®¢áª¨¬ ¡¨®á®¬.
Š®­ªà¥â­ë¥  ¤à¥á  ¯®à⮢, ¨á¯®«ì§ã¥¬ë¥ ¢ Sprinter-¥.
‡¤¥áì ï ¯à¨¢¥¤ã  ¤à¥á æ¨î ¯®à⮢ ¤«ï ª®­ä¨£ãà æ¨© Sprinter-1 ¨
Sprinter-2. ‘ࠧ㠮⬥çã, çâ® í⨠ ¤à¥á  «¥£ª® ¬®£ãâ ¡ëâì ¨§¬¥­¥­ë ¯à®á⮩
¯à®£à ¬¬®©, ¢ á«ãç ¥ ¯®ï¢«¥­¨ï â ª®© ­¥®¡å®¤¨¬®áâ¨.
‘â ­¤ àâ­ë¥ ¯®àâë.
#FE - RD_KBD - ¯®àâ ª« ¢¨ âãàë
#FE - WR_BRD - ¯®àâ ¡®à¤îà 
#7FFD - ¯®àâ à áè¨à¥­¨ï ZX-Spectrum 128k
#1FFD - ¯®àâ à áè¨à¥­¨ï Scorpion ZS-256
#1F,#0F - RD_KEMPS - ¯®àâ ¤¦®©á⨪ . ‚ ª®­ä¨£ãà æ¨¨ Sprinter-1 ¯®àâ
#1F  ¯¯ à â­® ¯¥à¥ ¤à¥áã¥âáï ­  ¯®àâ #0F
#BFFD,#FFFD - AY-PORTS - ¯®àâë AY-á®¯à®æ¥áá®à  (ZX-Spectrum-256/AY)
¥ ᮢᥬ áâ ­¤ àâ­ë¥ ¯®àâë.
#FB,#4F - ¯®àâ COVOX- .
„®¯®«­¨â¥«ì­ë¥ 8-¡¨â­ë¥ ¯®àâë Sprinter- .
#82 - PAGE0 - áâà ­¨æ  އ“, ¯®¤ª«îç ¥¬ ï ¢¬¥áâ® ‡“ ç¥à¥§ ¯®àâ #1FFD
#A2 - PAGE1 - áâà ­¨æ  އ“, ¯®¤ª«î祭­ ï ¯®  ¤à¥áã #4000
#C2 - PAGE2 - áâà ­¨æ  އ“, ¯®¤ª«î祭­ ï ¯®  ¤à¥áã #8000
#E2 - PAGE2 - áâà ­¨æ  އ“, ¯®¤ª«î祭­ ï ¯®  ¤à¥áã #C000
‡¤¥áì ­ ¤® ®â¬¥â¨âì ®á®¡®, ç¥à¥§ ¯®àâ #E2 ¬®¦­® ¨§¬¥­¨âì «î¡ãî ¨§
16-⨠áâà ­¨æ ᪮௨®­®¢áª®£® à á¯à¥¤¥«¥­¨ï ¯ ¬ïâ¨.
#89 - PORT_Y - ¢¥à⨪ «ì­ ï ª®®à¤¨­ â  â®çª¨ ­  £à ä¨ç¥áª®¬ íªà ­¥
¨«¨ áâà ­¨æ  VIDEO-RAM ¤«ï ᯥªâà㬮¢áª®£® ०¨¬ 
#C9 - RGMOD - ¯®àâ ०¨¬  íªà ­ . ¥à¥ª«îç ¥â áâà ­¨æë ०¨¬  íªà ­ .
#3C,#7C - SYS_PORT - á¨á⥬­ë© ¯®àâ âண âì ­¥ ४®¬¥­¤ã¥âáï
#10..#1F,#EE,#EF,#F0,#F1,#F4 - ¢­ãâ७­¨¥ ¯®àâë Z84C15
®àâë áâà ­¨æ އ“ ®âªàëâë ª ª ­  § ¯¨áì, â ª ¨ ­  ç⥭¨¥. â®
¯®§¢®«ï¥â «¥£ª® ¢ë¯®«­ïâì ¯à®£à ¬¬ë, ¨á¯®«ì§ãî騥 ¯¥à¥ª«î祭¨¥ áâà ­¨æ,  
§ â¥¬ ¢®§¢à é âì í⨠áâà ­¨æë ­ § ¤. ਠࠡ®â¥ BIOS-  ¢á¥ áâà ­¨æë
á®åà ­ïîâáï.
„®¯®«­¨â¥«ì­ë¥ 16-⨡¨â­ë¥ ¯®àâë Sprinter- .
#xx50..#xx55 - ¯®àâë HDD - ¨á¯®«ì§®¢ âì ¢­¥è­¨¬¨ ¯à®£à ¬¬ ¬¨ ­¥
४®¬¥­¤ã¥âáï. ”㭪樨 à ¡®âë á HDD § ¯¨á ­ë ¢ ‡“.
‘ªàëâë¥ ¯®àâë Sprinter- .
‘ªàëâ묨 ïîâáï ¯®àâë ª®â®àë¥ ­¤®áâã¯­ë ¢ ª®­ªà¥â­ë© ¬®¬¥­â
¢à¥¬¥­¨, ­® ¬®£ãâ áâ âì ¤®áâ㯭묨 ¯®á«¥ ¯à®¢¥¤¥­¨ï ¨§¬¥­¥­¨© ¢ ª à⥠¯®à⮢.
ˆå  ¤à¥á  ­¥ 㪠§ë¢ îâáï, â ª ª ª ®­¨ ¬®£ãâ ¡ëâì ¢ëáâ ¢«¥­ë ¢ «î¡®¥ ¬¥áâ®.
®àâ ‡“ BASIC48
®àâ ‡“ BASIC128
®àâ ‡“ TR-DOS
®àâ ‡“ EXPANSION
®àâ ‡“ SYSTEM
—¥à¥§ í⨠¯®àâë ¬®¦­® ãáâ ­®¢¨âì ­®¢ë¥ ¯à®è¨¢ª¨ ‡“. „«ï í⮣® ¨å
¤®áâ â®ç­® § ¯¨á âì ¢ އ“ á ­®¬¥à ¬¨ áâà ­¨æ ¬¥­ìè¥ #80 ¨ § ¯¨á âì ¢
ᮮ⢥âáâ¢ãî騩 ¯®àâ ­®¬¥à í⮩ áâà ­¨æë. ਠ⠪®¬ ¯®¤ª«î祭¨¨ áâà ­¨æë
⨠áâà ­¨æë ¡ã¤ãâ § é¨é¥­ë ®â § ¯¨á¨.
— áâ¨ç­® áªàëâ묨, â ª ¦¥ ïîâáï ¨ ¯®àâë #7FFD,#1FFD ¢ ®¡ëç­®¬
á®áâ®ï­¨¨ ®­¨ ¤®áâ㯭ë ⮫쪮 ­  § ¯¨áì, ­® §­ ç¥­¨ï, § ¯¨á뢠¥¬ë¥ ¢ íâ¨
¯®àâë ¬®¦­® ¯à®ç¨â âì, ®âªàë¢ á®®â¢¥âáâ¢ãî騥 ¯®àâë ­  ç⥭¨¥.
‚ ¤àã£¨å ª®­ä¨£ãà æ¨ïå ¬®¦¥â ®âáãâá⢮¢ âì ç áâì ¯®à⮢ ¨«¨
¯à¨áãâá⢮¢ âì ­®¢ë¥ ¯®àâë.
Žà£ ­¨§ æ¨ï ¢¨¤¥®¯ ¬ï⨠¨ ¢¨¤¥®à¥¦¨¬®¢.
‚¨¤¥®-އ“ Sprinter-  á®áâ ¢«ï¥â 256 ª¨«®¡ ©â. ‚ ¤ «ì­¥©è¥¬
¯à¥¤¯®« £ ¥âáï ¥£® à áè¨à¥­¨¥ ¤® 512 ª¨«®¡ ©â, ¤«ï ¯®«ã祭¨ï ¡®«¥¥
¢ë᮪¨å ०¨¬®¢ à §à¥è¥­¨ï.
‚ ०¨¬¥ Spectrum-®¢áª®£® íªà ­  ¢áï ®à£ ­¨§ æ¨ï â ª ï ¦¥ ª ª ¢
áâ ­¤ àâ­®¬ ZX-Spectrum. ‚ ®áâ «ì­ëå ०¨¬ å ¢ª«îç ¥âáï Sprinter-®¢áª¨©
íªà ­, áâàãªâãà  ª®â®à®£® ¢ª«î砥⠢ á¥¡ï ‘¯¥ªâà㬮¢áª¨© íªà ­ ª ª ç áâì
á奬ë.
“áâனá⢮ íªà ­ .
‚¥áì íªà ­ à §¡¨â ­  ª¢ ¤à âë, à §¬¥à®¬ ¢ áâ ­¤ àâ­®¥
‘¯¥ªâà㬮¢áª®¥ §­ ª®¬¥áâ®. „«ï ª ¦¤®£® ª¢ ¤à â  ãáâ ­ ¢«¨¢ ¥âáï ᢮©
ᮡá⢥­­ë© ०¨¬ ¢ë¢®¤ ,   â ª ¦¥  ¤à¥á ¢¨¤¥®-އ“, ®âªã¤  ¯à®¨§¢®¤¨âáï
¢ë¢®¤ ¢ íâ®â ª¢ ¤à â.
‚ ª ¦¤®¬ §­ ª®¬¥á⥠¬®¦¥â ¡ëâì § ¤ ­ ᢮© ᮡá⢥­­ë© ०¨¬ ¢ë¢®¤ .
‚ ¤ ­­ë© ¬®¬¥­â ¬®¦­® ãáâ ­ ¢«¨¢ âì â ª¨¥ ०¨¬ë:
ZX-40 - ®¡ëç­ë© ᯥªâà㬮¢áª¨© ०¨¬ á ®¤­¨¬ ¡¨â¯« ­®¬ ¨
®¤­¨¬  âਡã⮬ ­  §­ ª®¬¥áâ®.
ZX-80 - ¥¦¨¬, ¯®å®¦¨© ­  ᯥªâà㬮¢áª¨© ¯® áâ஥­¨î
ᨬ¢®«®¢, ­® ¢ ª ¦¤®¬ §­ ª®¬¥á⥠®ª §ë¢ ¥âáï ¤¢  ᨬ¢®« , ᦠâë¥ ¯®
£®à¨§®­â «¨.
G256-8 - ƒà ä¨ç¥áª¨© ०¨¬. Š¢ ¤à â ¯à¥¤áâ ¢«ï¥â ᮡ®© ¬ áᨢ 8x8
â®ç¥ª. ‚ ª ¦¤®© â®çª¥ § ¤ ¥âáï ®¤¨­ ¨§ 256-⨠梥⮢, ¢ë¡¨à ¥¬ëå ¨§ ¯ «¨âàë
16 ¬¨««¨®­®¢ 梥⮢. Š¢ ¤à âë ¬®£ãâ ¨¬¥âì à §­ë¥ ¯ «¨âàë. ’ ª¨å ¯ «¨âà ¤«ï
०¨¬  G256-8 - ç¥âëà¥.
G16-16 - ƒà ä¨ç¥áª¨© ०¨¬. Š¢ ¤à â ¯à¥¤áâ ¢«ï¥â ᮡ®© ¬ áᨢ
16x8 â®ç¥ª. Š ¦¤ ï â®çª  ¨¬¥¥â ®¤¨­ ¨§ 16 梥⮢, ¢ë¡¨à ¥¬ëå ¨§ ¯ «¨âàë 16
¬¨««¨®­®¢ 梥⮢. ’ ª ¦¥, ª ª ¨ G256-8 ¢ ª¢ ¤à â¥ ¬®¦¥â ¡ëâì ãáâ ­®¢«¥­ 
®¤­  ¨§ 4-å ¯ «¨âà.  «¨âàë £à ä¨ç¥áª¨å ०¨¬®¢ ¯¥à¥á¥ª îâáï ¤àã£ á ¤à㣮¬.
 «¨âà  16-â¨æ¢¥â­®£® ०¨¬  íâ® ¯¥à¢ë¥ 16 梥⮢ ¨§ ¯ «¨âàë 256-â¨æ¢¥â­®£®.
BORDER - ‚ ª¢ ¤à â ¢ë¢®¤¨âáï æ¢¥â ¡®à¤¥à .
BLANK - Š¢ ¤à â £ á¨âáï - áâ ­®¢¨âáï ç¥à­ë¬.
Ž¡ê¥¬ ¤ ­­ëå ०¨¬  ª¢ ¤à â  á®áâ ¢«ï¥â 2 ¡ ©â ,
¯®í⮬㠨§¬¥­¥­¨¥ ०¨¬  ¢á¥£® íªà ­  ᢮¤¨âáï ª ¯¥à¥§ ¯¨á¨ 2.5 ª¨«®¡ ©â
¤ ­­ëå ¢ ¢¨¤¥®-އ“.
®¤®¡­ ï áâàãªâãà  íªà ­  ¯®§¢®«ï¥â «¥£ª® ¯à®¨§¢®¤¨âì áªà®««¨­£¨
ª ª ¢á¥£®, â ª ¨ ç á⥩ íªà ­  ¯® §­ ª®¬¥áâ ¬.
¥¦¨¬ íªà ­  ãáâ ­ ¢«¨¢ ¥âáï ¯à¨ ¢ª«î祭¨¨,   â ª ¦¥ á ¯®¬®éìî
ä㭪権 ¡¨®á . ”㭪樨 ¡¨®á  ¯®§¢®«ïîâ ®âªà뢠âì ­  íªà ­¥ £à ä¨ç¥áª¨¥ ¨
⥪áâ®¢ë¥ ®ª­  ¢ ­ã¦­ëå ¬¥áâ å ¨ ­ã¦­®£® à §¬¥à .
‚ ¡¨®á¥ ¨¬¥îâáï ä㭪樨 ®âªàëâ¨ï £à ä¨ç¥áª®£® íªà ­  ­  ¢¥áì íªà ­
320x256 â®ç¥ª. ®á«¥ ®âªàëâ¨ï í⮣® ०¨¬  íªà ­ ¯à¥¤áâ ¢«ï¥â ᮡ®©.
­ ¡®à ¨§ 256-⨠«¨­¨©, ¤«¨­®© ¯® 320 ¡ ©â. ‘®á¥¤­¨¥ â®çª¨ ¢ «¨­¨¨ - íâ®
á®á¥¤­¨¥ ¡ ©âë. ¥à¥ª«î祭¨¥ «¨­¨© ¯à®¨§¢®¤¨âáï ç¥à¥§ PORT_Y, ¢ ª®â®à®¬
ãáâ ­ ¢«¨¢ ¥âáï ­®¬¥à «¨­¨¨, ¢ë¢®¤¨¬®© ­  íªà ­. ®¬¥à  «¨­¨© áç¨â îâáï
ᢥàåã íªà ­ , ­ ç¨­ ï á ­ã«¥¢®©.
„«ï ¢ë¢®¤  ¢ £à ä¨ç¥áª¨© íªà ­ â ª ¦¥ âॡã¥âáï ®âªàëâì
ᮮ⢥âáâ¢ãîéãî áâà ­¨æã ®á­®¢­®£® އ“. ‚ í⮩ áâà ­¨æ¥ ¡ã¤¥â ᮤ¥à¦ âìáï
ª®¯¨ï ¢¨¤¥®¨§®¡à ¦¥­¨ï.
‚¨¤¥®-އ“ ï¥âáï ⥭¥¢ë¬ އ“, ¯®í⮬㠨­ä®à¬ æ¨ï, ­ å®¤ïé ïáï ¢
®á­®¢­®¬ އ“, ¯®¤ ª®â®àë¬ ­ å®¤¨âáï ¢¨¤¥®-އ“ ­¥ ®¡ï§ â¥«ì­® ¡ã¤¥â
ᮢ¯ ¤ âì á ¨­ä®à¬ æ¨¥©, ­ å®¤ï饩áï ¢ í⮬ ¢¨¤¥®-އ“. ‡ ¯¨áì ¢¨¤¥®-¤ ­­ëå
¬®¦¥â ¯à®¨§¢®¤¨âìáï ¨ ¡¥§ ¯¥à¥§ ¯¨á¨ ¤ ­­ëå ¢ ®á­¢­®¬ އ“, çâ® ®ª §ë¢ ¥âáï
¯®«¥§­ë¬ ¯à¨ à ¡®â¥, ­ ¯à¨¬¥à, á® á¯à ©â ¬¨. „«ï à ¡®âë á® á¯à ©â ¬¨ â ª ¦¥
¯à¥¤ãᬮâ७ ०¨¬ § ¯¨á¨ ¢ ¢¨¤¥®-އ“ á ¯à®§à ç­ë¬ 梥⮬. ‚ í⮬ ०¨¬¥
¨­ä®à¬ æ¨ï, ¯¥à¥¤ ¢ ¥¬ ï ¢ ¢¨¤¥®-އ“ ¯à®¢¥àï¥âáï ­  ­ «¨ç¨¥ ¡ ©â  #FF. …᫨
íâ®â ¡ ©â ®¡­ à㦨¢ ¥âáï, ⮠横« § ¯¨á¨ ¯à®¯ã᪠¥âáï ¨ ­  íªà ­¥ ¢ í⮬
¬¥á⥠®áâ ¥âáï ⥠¤ ­­ë¥, ª ª¨¥ ¡ë«¨ à ­¥¥. ’ ª¨¬ ®¡à §®¬ ­  íªà ­¥ ¬®¦­®
¡ëáâà® ¯à®à¨á®¢ë¢ âì á¯à ©âë, ¯à¥¤áâ ¢«ïî騥 ¨§ á¥¡ï ¯àאַ㣮«ì­ë¥ ª à⨭ª¨
á "¯à®§à ç­ë¬¨" 梥⠬¨.
ਬ¥à ¯à®£à ¬¬ë ¢ë¢®¤  ¯àאַ㣮«ì­®© ª à⨭ª¨ ­  íªà ­:
PAGE3 EQU #E2
RGADR EQU #89
LD A,#50 ; áâà ­¨æ  £à ä¨ç¥áª®£® ¢¨¤¥®íªà ­ 
OUT (PAGE3),A ; ãáâ ­®¢¨âì ¢ PAGE3
LD HL,Pucture ;  ¤à¥á ª à⨭ª¨ (àï¬ë¥ „ ­­ë¥)
LD DE,#C040+HorPlace ; ¯®«®¦¥­¨¥ ª à⨭ª¨ ­  íªà ­¥ ¯® £®à¨§®­â «¨
LD A,VerPlace ; ¯®«®¦¥­¨¥ ª à⨭ª¨ ­  íªà ­¥ ¯® ¢¥à⨪ «¨
OUT (RGADR),A
LD B,VerSize ; ¢ëá®â  ª à⨭ª¨
LOOP: PUSH DE ; § ¯®¬­¨âì ¯®«®¦¥­¨¥ ­  «¨­¨¨
PUSH BC ; § ¯®¬­¨âì áç¥â稪 ¢ëá®âë
LD BC,HorSize ; ¤«¨­  ª à⨭ª¨
LDIR ; ª®¯¨à®¢ âì «¨­¨î
POP BC
POP DE
INC A ; á«¥¤ãîé ï ª®®à¤¨­ â  ¯® Y
OUT (RGADR),A
DJNZ LOOP ; ¯®¢â®àïâì ­ã¦­®¥ ª®«¨ç¥á⢮ à §
“¯à ¢«¥­¨¥ ०¨¬®¬ ¢ë¢®¤  ­  íªà ­ (¢ª«î祭¨¥ ¢ë¢®¤  á ¯à®§à ç­ë¬¨
梥⠬¨, ®âª«î祭¨¥ ª®¯¨à®¢ ­¨ï ¢ ®á­®¢­®¥ އ“) ®áãé¥á⢫ï¥âáï ç¥à¥§
¬« ¤è¨¥ ¡¨âë ¯®àâ  áâà ­¨æë £à ä¨ç¥áª®£® íªà ­ .
€ªá¥«¥à â®à ®¯¥à æ¨© á Ž‡“.
€ªá¥«¥à â®à ®¯¥à æ¨© á Ž‡“ ¯à¥¤­ §­ ç¥­ ¤«ï ã᪮७¨ï ®¯¥à æ¨©
¯® ¯¥à¥á뫪¥ ¤ ­­ëå ¨«¨ ¯® § ¯®«­¥­¨î އ“ ®¤­¨¬ ¡ ©â®¬. €ªá¥«¥à â®à
¯à¨áãâáâ¢ã¥â ¢ ç¨áâ® Sprinter-®¢áª¨å ª®­ä¨£ãà æ¨ïå ¨ ¯®í⮬㠭¨ª ª ­¥
¬¥è ¥â à ¡®â¥ ®¡ëç­ëå Spectrum-®¢áª¨å ¯à®£à ¬¬.
Žá­®¢®©  ªá¥«¥à â®à  ï¥âáï ¡ëáâ஥ ¢­ãâ७­¥¥ އ“ ¢ ‹Œ.
ޝ¥à æ¨¨ ¯® ¯¥à¥á뫪¥ ¤ ­­ëå ¯à®¨§¢®¤ïâáï ¯ã⥬ § ¯¨á¨ ¡«®ª  ¤ ­­ëå ¢ íâ®
¢­ãâ७­¥¥ އ“,   § â¥¬ ª®¯¨à®¢ ­¨¨ ¥£® ¢ ­ã¦­®¥ ¬¥áâ® ¯ ¬ï⨠¨§ í⮣® އ“.
®á«¥ ®¤­®© § ¯¨á¨ ª®¯¨à®¢ ­¨¥ ¬®¦¥â ¯à®¨§¢®¤¨âìáï ­¥áª®«ìª® à § ¨ â ª¨¬
®¡à §®¬ ¬®¦­® ¯à®¨§¢®¤¨âì § ¯®«­¥­¨¥ íªà ­  ⥪áâãà ¬¨.
„«ï § ¯®«­¥­¨ï íªà ­  ®¤­¨¬ 梥⮬ ¨á¯®«ì§ã¥âáï ¤à㣮© ०¨¬
 ªá¥«¥à â®à . ‚ ­¥¬ ¢¬¥áâ® ª®¯¨à㥬®£® ¡«®ª  ¤ ­­ëå ¨§ ¢­ãâ७­¥£® އ“
¯à®¨§¢®¤¨âáï § ¯¨áì ¤ ­­ëå á è¨­ë ¯à®æ¥áá®à , ª®â®àë¥ ¢ íâ®â ¬®¬¥­â ­¥
¨§¬¥­ïîâáï.
«®ª ¤ ­­ëå, § ¯¨á뢠¥¬ë© ¢ އ“  ªá¥«¥à â®à  ¬®¦¥â ¨¬¥âì à §«¨ç­ãî
¤«¨­­ã ¨§ ¤¨ ¯ §®­  1..256 ¡ ©â.
“¯à ¢«¥­¨¥  ªá¥«¥à â®à®¬ ¯à®¨§¢®¤¨âáï ­¥¯®á।á⢥­­® ¨§ ¯à®£à ¬¬ë.
„«ï í⮣® ¨§¯®«ì§ãîâáï ª®¬ ­¤ë ¯à®æ¥áá®à , ª®â®àë¥ ä ªâ¨ç¥áª¨ ïîâáï
®¯¥à æ¨ï¬¨ ⨯  NOP.
â® ª®¬ ­¤ë LD A,A; LD B,B; LD C,C; LD D,D; LD E,E; LD H,H, LD L,L
 §­ ç¥­¨¥ ª®¬ ­¤ á«¥¤ãî饥:
LD B,B - ¢ëª«îç¨âì  ªá¥«¥â à®à.
LD D,D - ¢ª«îç¨âì  ªá¥«¥à â®à ¢ ०¨¬ ¯à¨¥¬  ¡ ©â  à §¬¥à  ¡«®ª 
¤ «¥¥ á«¥¤ã¥â ª®¬ ­¤  ⨯  LD A,dat, £¤¥ dat ¨ ¡ã¤¥â ­®¢ë¬
à §¬¥à®¬ ¡«®ª . …᫨ à §¬¥à ¡«®ª  ¡ë« ãáâ ­®¢«¥­ à ­¥¥,
¥£® ¬®¦­® ­¥ ãáâ ­ ¢«¨¢ âì.
LD C,C - ޝ¥à æ¨ï Fill - § ¯®«­¥­¨¥ ®¤­¨¬ ¡ ©â®¬. ®á«¥¤ãîé ï
ª®¬ ­¤  ⨯  LD (HL),A ¯à¨¢¥¤¥â ª § ¯®«­¥­¨î 㪠§ ­­®£®
à ­¥¥ ª®«¨ç¥á⢠ ¡ ©â §­ ç¥­¨¥¬ A
LD E,E - ޝ¥à æ¨ï Fill ¤«ï £à ä¨ç¥áª®£® íªà ­  - § ¯®«­¥­¨¥
¢¥à⨪ «ì­ëå «¨­¨©.
LD H,H - rezerved
LD L,L - ª®¯¨à®¢ ­¨¥ ¡«®ª . ®á«¥¤ãîé ï ª®¬ ­¤  ⨯  LD A,(HL)
¯à¨¢¥¤¥â ª § ¯®«­¥­¨î އ“  ªá¥«¥à â®à  ¤ ­­ë¬¨ ¨§  ¤à¥á  (HL),
  ª®¬ ­¤  ⨯  LD (DE),A ¯à¨¢¥¤¥â ª ¯¥à¥§ ¯¨á¨ ¤ ­­ëå ¨§ އ“
 ªá¥«¥à â®à  ¢ ®á­®¢­®¥ ¨«¨ ¢¨¤¥®-އ“.
LD A,A - ª®¯¨à®¢ ­¨¥ ¡«®ª  ¤«ï £à ä¨ç¥áª®£® íªà ­  ¯®¤®¡­  ª®¬ ­¤¥
LD L,L, ­® à ¡®â ¥â á ¢¥à⨪ «ì­ë¬¨ «¨­¨ï¬¨ íªà ­ .
ਬ¥à ¨á¯®«ì§®¢ ­¨ï  ªá¥«¥à â®à :
; ‘ç¨â ¥¬, çâ® íªà ­­ ï áâà ­¨æ  㦥 ®âªàëâ  ¯®  ¤à¥áã #C000
LD HL,#C040 ;  ¤à¥á ­ ç «  «¨­¨¨ ¯¥à¢®£® íªà ­ 
LD DE,#C180 ;  ¤à¥á ­ ç «  «¨­¨¨ ¢â®à®£® íªà ­ 
LD BC,#140 ; ¤«¨­  íªà ­  ¯® £®à¨§®­â «¨
DI ; § ¯à¥â¨âì ¯à¥à뢠­¨ï ¤«ï à ¡®âë á  ªá¥«¥à â®à®¬
LD D,D ; ¢ª«îç¨âì  ªá¥«¥à â®à ­  ãáâ ­®¢ªã à §¬¥à  ¡«®ª 
LD A,0 ; ãáâ ­®¢¨âì à §¬¥à ¡«®ª  - 256 ¡ ©â
LD A,A ; ãáâ ­®¢¨âì  ªá¥«¥à â®à ­  ª®¯¨à®¢ ­¨¥
; ¢¥à⨪ «ì­ëå «¨­¨©.
LDIR ; ª®¯¨à®¢ âì !
LD B,B ; ¢ëª«îç¨âì  ªá¥«¥à â®à
EI ; ¢ª«îç¨âì ¯à¥à뢠­¨ï
â®â ®â१®ª ¯à®£à ¬¬ë ¯à®¨§¢¥¤¥â ª®¯¨à®¢ ­¨¥ ¢á¥£® íªà ­  á ®¤­®£®
íªà ­  ­  ¤à㣮©. ‚à¥¬ï ¥£® ¨á¯®«¥­¨ï á®áâ ¢«ï¥â ¯à¨¬¥à­® 1.2 ¨­â .
„®¯®«­¨â¥«ì­ë¥ ä㭪樨  ªá¥«¥à â®à  ¯®ï¢«ïî騥áï ¢ ª®­ä¨£ãà æ¨¨
Sprinter-3 à ¡®â îâ ¯®¤®¡­ë¬ ¦¥ ®¡à §®¬. „«ï ¢ë¯®«­¥­¨ï «®£¨ç¥áª¨å ä㭪権
¨á¯®«ì§ãîâáï ª®¬ ­¤ë XOR (HL); OR (HL); AND (HL).
ਬ¥à ªá®àª¨ ¡«®ª  ¢ 256 ¡ ©â.
LD HL,ADRES_1
LD DE,XOR_DAT
DI
LD D,D
LD A,0 ; ç¨á«® ¡ ©â, ª®â®àë¥ ­ ¤® ¯à®ªá®à¨âì
LD L,L
LD A,(DE) ; ‚§ïâì ¡«®ª ¢ އ“  ªá¥«¥à â®à 
XOR (HL) ; ¯à®¨§¢¥á⨠®¯¥à æ¨î XOR á ¤ ­­ë¬¨  ªá¥«¥à â®à 
LD (HL),A ; § ¯®¬­¨âì ¢ އ“ १ã«ìâ â ®¯¥à æ¨¨
LD B,B
EI
‘ª®à®áâì à ¡®âë  ªá¥«¥à â®à  ®£à ­¨ç¨¢ ¥âáï ⮫쪮 䨧¨ç¥áª®©
᪮à®áâìî à ¡®âë ®á­®¢­®£® އ“. Ž¯à¥¤¥«¨âì ¢à¥¬ï à ¡®âë ª®¬ ­¤ë á
 ªá¥«¥à â®à®¬ ¬®¦­® ¯® â ª®© ¯à¨¬¥à­®© ä®à¬ã«¥:
‚६ï à ¡®âë = ¢à¥¬ï à ¡®âë ª®¬ ­¤ë ¡¥§  ªá¥«¥à â®à  + ¢à¥¬ï
à ¡®âë  ªá¥«¥à â®à 
‚६ï à ¡®âë  ªá¥«¥à â®à  = ç¨á«® ¯¥à¥áë« ¥¬ëå ¡ ©â /7000000 (ᥪ㭤)
Žâª«î祭¨¥ ¯à¥à뢠­¨© ¢® ¢à¥¬ï à ¡®âë  ªá¥«¥à â®à  ­¥®¡å®¤¨¬®, â ª
ª ª ¢ íâ®â ¬®¬¥­â ç áâ¨ç­® ¬¥­ï¥âáï á¨á⥬  ª®¬ ­¤ ¯à®æ¥áá®à  ¨ ¯à®£à ¬¬ 
­  ¯à¥à뢠­¨¨ ­¥ ᬮ¦¥â à ¡®â âì ­®à¬ «ì­®.
‡ ª«î祭¨¥.
 ¡®â  ­ ¤ Sprinter-®¬ ¯à®¤®«¦ ¥âáï. ‘®¢¥à襭áâ¢ã¥âáï ¦¥«¥§® ¨
¡¨®á. ¨è¥âáï á®äâ, ¯®¤¤¥à¦¨¢ î騩 à áè¨à¥­­ë¥ ०¨¬ë à ¡®âë ª®¬¯ìîâ¥à .
® ¢á¥¬ ª®¬¬¥àç¥áª¨¬ ¢®¯à®á ¬ á¢ï§ ­­ë¬ á ¯à¨®¡à¥â¥­¨¥¬ ª®¬¯ìîâ¥à 
¬®¦­® ®¡à é âìáï ¢ ä¨à¬ã "¥â¥àá":
Adress: ‘ ­ªâ-¥â¥à¡ãà£, ã«. ‚®ááâ ­¨ï, ¤. 35, ®ä. 31.
Phone: (812)-327-35-31
E-mail: peters@atlant.ru
® â¥å­¨ç¥áª¨¬ ¢®¯à®á ¬ ®¡à é âìáï ª® ¬­¥:
Fido: Ivan Mak (2:5030/529.24)
E-mail: ivan_mak@yahoo.com (¢à¥¬¥­­® § ªàëâ)
File diff suppressed because it is too large Load Diff
+272
View File
@@ -0,0 +1,272 @@
# 1. Архитектура компьютера Sprinter Sp2000
## 1.1 Общие сведения
ZX Sprinter — универсальный компьютер на базе 8-битного процессора Z80. Основой является перепрограммируемая логическая матрица (ППЛМ/CPLD/FPGA), что обеспечивает гибкую изменяемую архитектуру — конфигурация схемы может меняться непосредственно во время работы.
Материнская плата — Sp2000 (разработана в конце 2000 г.).
| Параметр | Значение |
|---|---|
| Процессор | Z84C15 (Z80-совместимый, внутренние PIO/SIO/CTC) |
| Тактовая частота | 21 МГц (turbo) / 3.5 МГц (slow) |
| ОЗУ (main) | 4 МБ (72-pin SIMM) |
| Быстрое ОЗУ (КЭШ-ОЗУ) | 64 КБ (SRAM, без wait states) |
| ПЗУ | 256 КБ (8 страниц по 32 КБ) |
| Видео-ОЗУ | 256 КБ |
| Контроллер дисков | КР1818ВГ93 (аналог WD1793) |
| Поддержка НЖМД | IDE/AT |
| Контроллер клавиатуры | AT-совместимая (101 key), PS/2 |
| Контроллер мыши | Serial MS-Mouse (RS-232 через Z84C15 SIO) |
| Слоты расширения | 2 × ISA-8 |
| Звук | AY-3-8910 (в ППЛМ), 16-бит ЦАП TDA1543 (COVOX + AY) |
| COVOX-Blaster (CBL) | 16-bit с буферным ОЗУ 256 байт, 15/22 кГц |
| CMOS-часы | Dallas DS12887A (опционально, эмулируется при отсутствии) |
| Видеовыход | Аналоговый CGA-монитор, RGB, TV (SCART) |
## 1.2 Системная архитектура
Упрощённая структура:
```
┌─────────────┐
┌──────────┐ │ ISA-8 │
│ Принтер │ │ слот 1..2 │
│ Мышь │ └──────┬──────┘
│ Джойстик │ │
└─────┬────┘ ┌─────┴──────┐
│ │ Буферы и │
┌─────┴──────┐ │ дешифраторы│
│ Z84C15 │ └──────┬─────┘
│ (CPU + │◄─────────┤
│ PIO/SIO/ ├──────────┤
│ CTC) ├──────────┤
└─────┬──────┘ │
│ ┌────┴──────────┐
┌─────┴──────┐ │ EP1K30QC208 │
│ ROM 256K │◄───►│ (CPLD/FPGA) │◄───► VRAM 256K
│ Cache 64K │ │ │◄───► Main RAM
└────────────┘ │ │
│ AY-3-8910 │
│ COVOX/CBL │
│ Accel. │
└──────┬────────┘
┌───┴───┐
│TDA1543│
│ ЦАП │
└───────┘
```
Периферия (FDD, HDD, Kempston, клавиатура AT) подключается через ППЛМ. Вспомогательная ППЛМ (EPM7064 на Sp2000) обеспечивает синхронизацию и начальный запуск. Дешифрация адресов портов выполняется через ППЛМ, что позволяет перепрограммировать адреса портов.
## 1.3 ППЛМ и загрузка конфигураций
### Механизм конфигурирования
При включении или RESET информация в ППЛМ стирается. ППЛМ переходит в режим ожидания загрузки блока данных конфигурации. Процессор отключён от периферии, в его адресное пространство включено ПЗУ и возможно КЭШ-ОЗУ. Любая запись процессора в адресное пространство в этот момент записывает данные в ППЛМ.
Программа загрузчика в ПЗУ:
1. Проверяет флаг в КЭШ-ОЗУ по смещению `#80` (последняя страница КЭШа)
2. Если флаг (строка `ACEX_30K_LOADING`) установлен → загружает конфигурацию из КЭШ-ОЗУ (смещение `#100`)
3. Если флаг сброшен → загружает конфигурацию из ПЗУ
### Смена конфигурации программно
1. Загрузить файл прошивки в последнюю страницу КЭШ-ОЗУ (смещение `#100`)
2. Записать флаг `ACEX_30K_LOADING` по смещению `#80`
3. Выполнить программный сброс записью в страницу RESET_PAGE (`#A0`),
4. После сброса загрузчик находит флаг, загружает прошивку в ППЛМ и стирает флаг
При аппаратном RESET флаг отсутствует — загружается начальная конфигурация из ПЗУ.
### Ограничения
- Внутренний формат данных ППЛМ — закрытая информация Altera
- Программа разводки схем — MAX+Plus II — не работает на ZX-Spectrum/Sprinter (требуется PC)
## 1.4 Конфигурации ППЛМ
### Sprinter-1
Максимально совместима с ZX-Spectrum. Включает:
- Режимы Spectrum-128/Scorpion-256/Pentagon-512
- Расширенная память до 4 МБ
- Расширенный экран: Spectrum, Text-80×32, Graf-320×256×256
- Контроллер дисковода (ВГ93), контроллер IDE
- AT-клавиатура, подключённая как ZX-Keyboard (через порт `#FE`)
- COVOX 8-bit
- AY-3-8910 (доступен)
### Sprinter-2
Требования к совместимости жёстче. Добавляет:
- Акселератор операций с ОЗУ (fill, copy, AND, OR, XOR)
- AT-клавиатура через внутренний последовательный порт Z84C15 (сканкоды, не ZX-матрица)
### ZX-Spectrum+AY
Максимальное приближение к ZX-Spectrum-128/256. AY-3-8910 (3-я версия, в ППЛМ): 3 голоса, шум, амплитуда, огибающая (обнаружена ошибка в формирователе огибающей [IvanMak.txt:224229]).
### Game-1
На базе Sprinter-2. Акселератор без логических функций. COVOX-Blaster (15 кГц).
### DooM
Развитие Game-1. Акселератор с аппаратным растяжением/сжатием вертикальных и горизонтальных линий.
### Video
Похожа на Game-1, добавлена возможность передачи данных с HDD прямо в видео-память. Режим `GR-256-4×4` — 160×128 с аппаратным удвоением пикселя.
### Плата Sp2000
Конфигурации Sprinter-1, Sprinter-2 и ZX+AY объединены в одну прошивку. Переключение — через системный порт. Game-1, DooM, Video также планируется свести в одну.
## 1.5 Система памяти (обзор)
Адресное пространство Z80 (64 КБ) разделено на 4 окна по 16 КБ:
| Окно | Адреса | Название | Назначение (по умолчанию) |
|---|---|---|---|
| W0 | `#0000..#3FFF` | PAGE0 | ПЗУ (системное) |
| W1 | `#4000..#7FFF` | PAGE1 | ОЗУ, страница 5 |
| W2 | `#8000..#BFFF` | PAGE2 | ОЗУ, страница 2 |
| W3 | `#C000..#FFFF` | PAGE3 | ОЗУ, любая страница (0..N) |
Основная память — 4..64 МБ — делится на блоки по 16 КБ. Управление через порты страниц (`#82`, `#A2`, `#C2`, `#E2` для окон 0..3). ПЗУ: `#E0..#EF`. КЭШ-ОЗУ: `#F0..#FF`.
Детально — `04-memory.md`.
## 1.6 Видеосистема (обзор)
Основа — ППЛМ + 256 КБ видео-ОЗУ. Два режима адресации VRAM:
- **Спектрумовский** — экран 32 блока по 8 КБ, адрес через RGADR
- **Графический** — 256 строк × 1024 байта, линейная адресация; страницы `#50..#5F`
Режимы вывода (задаются для каждого квадратика 8×8):
- ZX-40 — текстовый 40×32 (Spectrum-совместимый)
- ZX-80 — текстовый 80×32, до 36 знакогенераторов
- GR-256-8 — графический 320×256×256 цветов
- GR-16-16 — графический 640×256×16 цветов
Палитра: 8 палитр по 256 цветов из 16 млн. Цвет: 3 байта RGB (BGR) на цвет.
четыре из этих восьми палитр используются для графических режимов и оставшиеся четыре для текстовой палитры — 4 текстовые палитры (paper, ink, flash-paper, flash-ink) по адресам `#03F0..#03FE`.
Детально — `05-graphics.md`.
## 1.7 Акселератор (обзор)
Акселератор — быстрое внутреннее ОЗУ в ППЛМ. Присутствует в Sprinter-2 и выше. Управляется через NOP-команды процессора (`LD D,D`, `LD L,L`, `LD C,C` и др.), которые ППЛМ распознаёт как инструкции акселератора.
Операции: fill, copy, AND, OR, XOR.
**время копирования полного экрана.** - «~1.2 инта» (~24 мс при 50 Гц)
**формула скорости.**
«Время работы акселератора = число пересылаемых байт / 7000000 (секунд)»
**акселератор и прерывания.** требуется DI/EI (акселератор сильно меняет систему команд).
Детально — `06-accel.md`.
## 1.8 Быстрое ОЗУ (КЭШ-ОЗУ)
64 КБ SRAM с доступом без wait states на частоте 21 МГц. Физические страницы `#F0..#FF`, реально 4 страницы по 16 КБ.
Подключается через порт `#FB` (стандартный Pentagon-овский), чтение `IN A,(#FB)` — включает, `IN A,(#7B)` — выключает.
Ограничения:
- Несовместимо с акселератором
- Не сохраняется между процессами
- Конфликт с COVOX по порту `#FB`
- На Sp2000 используется для загрузки прошивок ППЛМ и как временное хранилище
Детально — `04-memory.md`.
## 1.9 ISA-слоты
Два 8-битных ISA-слота.
Возможны два способа доступа к ISA — вероятно, разные конфигурации ППЛМ:
- Через страницы памяти `#D0..#DF` [IvanMak.txt:855858]: «Бит 1 номера означает выбор доступа к порту или памяти ISA, а бит 2 определяет к какому из двух слотов осуществляется доступ.»
- Через управляющие порты [Parinov.txt:798857]: (1) запись `#10` в порт `1FFDh`, (2) запись управляющего байта в порт `0E2h` (бит D2=слот, D1=порт/память), (3) использование порта `9FBDh` для старших адресов.
## 1.10 Звук (обзор)
COVOX: ЦАП TDA1543 — 16-битный стерео, COVOX — 8-битный, реально используется 10 бит для одновременного вывода COVOX и AY.
**порт CBL.**
«Порт управления: 004Eh (16-bit port!!!, писать только через OUT (C),reg)» — 16-бит, требуется `OUT (C),A`.
Компоненты звукового тракта:
- **16-битный ЦАП TDA1543** (реально 10 бит) — подключён к ППЛМ
- **AY-3-8910** — эмуляция в ППЛМ (3-я версия: 3 голоса + шум + огибающая)
- **COVOX 8-bit** — порты `#FB` / `#4F`, через TDA1543
- **Бипер** — бит 3 порта `#FE`
- **COVOX-Blaster (CBL)** — COVOX с буферным ОЗУ 256 байт, 15/22 кГц. Управление порт `#4E`
Детально — `08-io.md`.
## 1.11 Клавиатура (обзор)
В Sprinter-1 — ZX-клавиатурная матрица через порт `#FE`. В Sprinter-2 и выше — AT-клавиатура через внутренний SIO Z84C15 (порты `#18`/`#19`). Сканкоды напрямую, FIFO на 3 байта.
Детально — `08-io.md`.
## 1.12 Мышь
Microsoft Mouse (2 кнопки) через последовательный порт Z84C15. Порт команд: `#1B`, порт данных: `#1A`.
Детально — `08-io.md`.
## 1.13 Дисковая подсистема
- **FDD**: КР1818ВГ93 (WD1793). Порт `#1F` перенаправляется на `#0F` в ПЗУ. Поддержка 720 КБ / 1.44 МБ.
- **HDD**: IDE/AT. Порты `#XX50..#XX55`, регистры `#20..#29`.
- **CMOS Dallas DS12887A**: порты `#FFBD` (read), `#BFBD` (write), `#DFBD` (address).
Детально — `08-io.md`.
## 1.14 Система прерываний (обзор)
Поддерживаются IM1 и IM2. Генерация INT — через бит 0 порта `#FE`. CTC Z84C15 — таймер, SIO — клавиатура. ISA-слоты — IRQ через PIO порт B (`#1F`).
Детально — `07-irq.md`.
## 1.15 Сброс и старт машины
Программный сброс (без перезагрузки ППЛМ) [IvanMak.txt:13851394]:
```
DI
LD A, 16
LD BC, 1FFDh
OUT (C), A
LD A, 0A0h
OUT (PAGE3), A ; PAGE3 = #E2
LD (0C000h), A ; в этот момент происходит RESET
DI
HALT
```
Сброс с перезагрузкой ППЛМ — через функции BIOS (`02-bios.md`). Аппаратный RESET — начальная конфигурация из ПЗУ.
## 1.16 Плата Sp2000 — особенности
- Конфигурации объединены в две прошивки ППЛМ (Sprinter-1+Sprinter-2+ZX+AY — одна; Game-1+DooM+Video — другая)
- AY-сопроцессор доступен и в Sprinter-1, и в Sprinter-2
- ОЗУ: 72-pin SIMM, от 4 до 64 МБ
- Внутренние порты Z84C15: `#10..#1F`, `#EE`, `#EF`, `#F0`, `#F1`, `#F4` — их адреса вне ППЛМ
- Обнаружена ошибка в формирователе огибающей AY (3-я версия)
## 1.17 Кросс-ссылки
- BIOS: `02-bios.md`
- DSS/ESTEX: `03-dss.md`
- Память, режимы, банки: `04-memory.md`
- Видео, палитра: `05-graphics.md`
- Акселератор: `06-accel.md`
- Прерывания: `07-irq.md`
- Диски, джойстик, принтер, ISA: `08-io.md`
- Клавиатура, мышь: `09-input.md`
- Звук: `10-sound.md`
- Карта портов: `11-ports.md`
- Баги: `12-bugs.md`
+1234
View File
File diff suppressed because it is too large Load Diff
+1082
View File
File diff suppressed because it is too large Load Diff
+387
View File
@@ -0,0 +1,387 @@
# 4. Управление памятью Sprinter Sp2000
## 4.1 Аппаратная модель памяти
Z80 видит 64 КБ, разбитые на **четыре окна (window) по 16 КБ**. Каждое окно независимо маппится на физическую страницу (page) ОЗУ или ПЗУ через порт-регистр страницы:
| Окно | Адреса | Порт страницы | Назначение |
|------|--------|---------------|------------|
| W0 | `#0000..#3FFF` | `#82` (PAGE0, ROM.SLOT0) | RST-обработчики, BIOS/DSS ядро |
| W1 | `#4000..#7FFF` | `#A2` (SLOT1, PAGE1) | Приложение |
| W2 | `#8000..#BFFF` | `#C2` (SLOT2, PAGE2) | **Стек — обязательно здесь** |
| W3 | `#C000..#FFFF` | `#E2` (SLOT3, PAGE3) | Разделяемые страницы DSS, графика, резидентный код |
- Запись в порт `#82`/`#A2`/`#C2`/`#E2` меняет физическую страницу окна; чтение возвращает текущую.
- Для совместимости со старыми моделями (ZX-Spectrum 128, Scorpion, Pentagon) работают также порты `#7FFD`, `#1FFD`, `#DFFD`, `#EFF7`:
| Порт | Описание |
|------|----------|
| `#7FFD` | Биты 0-2: страница W3; бит 3: выбор ПЗУ (0=DOS, 1=Basic/48) |
| `#1FFD` | Биты 0-2: страница W0; бит 3: дополнительная память; бит 4: турбо |
| `#DFFD` | Страница W1 (Pentagon, когда бит 5 порта `#7FFD`=0) |
| `#EFF7` | Страница W2 (Pentagon) |
> **Страница `#FF`:** если окно не выделено программе (образ меньше 16/32 КБ), DSS маппит его на специальную страницу `#FF`. Чтение даёт `#FF`, запись игнорируется. Это ключевая причина «молчаливой» порчи данных — см. §4.9.
> **Физические страницы:** порты `#82`/`#A2`/`#C2`/`#E2` адресуют физические 16-КБ страницы в адресном пространстве Sprinter. Диапазон страниц зависит от установленной памяти (обычно 0..127 для 2 МБ, 0..255 для 4 МБ). Старшие страницы заняты видео-ОЗУ и ПЗУ.
---
## 4.2 Окно W0 (#0000..#3FFF) — системное
W0 — критическое окно: в нём живут обработчики `RST 08h` (BIOS), `RST 10h` (DSS/ESTEX) и `RST 18h` (BIOS-EXP). Пока в W0 стоит системная страница, вызовы доступны.
**ROM/RAM switching:**
| Действие | Код |
|----------|-----|
| Включить ПЗУ в W0 | `OUT (#7C),A` (SYS_PORT.ROM) |
| Выключить ПЗУ, вернуть ОЗУ | `OUT (#3C),A` (SYS_PORT.RAM) |
При включённом ПЗУ в W0 отображается системная страница BIOS. Для вызова RST-функций W0 должен быть в ПЗУ — BIOS сам переключает его при входе через `RST 08h`.
**Требования к прерываниям:**
Sprinter работает в режиме **IM1** — вектор прерывания фиксирован на `#0038` (всегда в ПЗУ/ОЗУ W0). Обработчик EXP.asm по адресу `#0038`:
1. Переключает W3 на SYS_PAGE.
2. Проверяет флаг `INT_ID` = `#AA`.
3. Если есть пользовательский обработчик — вызывает его (адрес из SYS_PAGE).
4. Восстанавливает W3, `EI`, `RETI`.
Регистр `I` инициализирован в `#3F` (наследие ZX-Spectrum), но при IM1 не используется для диспетчеризации. Если программа переключается в **IM2**`I` и расположение таблицы векторов определяются программой произвольно, стандартного расположения нет.
**DSS и W0:** при вызове `RST 10h` DSS переключает W0 на свою рабочую страницу (COREPAGE = 4), выполняет функцию и восстанавливает W0. Если в W0 до вызова была ОЗУ, DSS восстановит её; если ПЗУ — вернёт ПЗУ.
**Практические рекомендации:**
- Для вызова системных функций держите W0 в ПЗУ (или не заботьтесь — RST 08h и RST 10h его переключат сами).
- При IM1 (режим по умолчанию) прерывание идёт на `#0038` — это ПЗУ, переключение W0 не требуется.
- При IM2 программа сама управляет таблицей векторов — не забывайте про `DI`/`EI` при переключении W0.
**Размещение своей RAM-страницы в W0:**
Если программе нужно дополнительное окно (например, для атласов спрайтов или кода,
не требующего системных вызовов), можно временно маппить свою страницу в W0. Для
безопасной работы страница должна содержать корректную таблицу RST-векторов в
первые `#40` байт:
| Адрес | Инструкция | Назначение |
|-------|-----------|------------|
| `#0000` | `JP boot` или `RST 00h` (обычно не используется) | |
| `#0008` | `JP rst08_stub` | RST 08h — BIOS |
| `#0010` | `JP rst10_stub` | RST 10h — ESTEX/DSS |
| `#0018` | `JP rst18_stub` | RST 18h — BIOS-EXP |
| `#0020..#0030` | `JP _w0_unused_stub` | RST 20h-30h — не используются |
| `#0038` | `JP isr_stub` | IM1 (RST 38h) |
| `#0066` | `RETN` или `JP nmi_stub` | NMI |
Каждый стаб (в этой же странице, выше `#40`) выполняет одну задачу: сохранить
текущий номер страницы W0, переключить W0 обратно на ПЗУ (`OUT (#7C),A`
SYS_PORT.ROM), выполнить настоящий системный вызов, после чего восстановить
страницу программы и вернуться к исходному caller'у. Типовой паттерн для ESTEX
(`RST 10h`):
```asm
rst10_stub:
IN A, (#82) ; сохранить свою страницу W0
PUSH AF
OUT (#7C), A ; включить ПЗУ в W0 (SYS_PORT.ROM)
; теперь в W0 — системная страница, RST 10h будет обработан корректно
RST #10 ; вызвать настоящий ESTEX (DSS сам переключит W0
; на COREPAGE и восстановит ПЗУ на выходе)
POP BC
LD A, B
OUT (#82), A ; вернуть свою страницу в W0
RET ; возврат к исходному caller'у
```
Такой же паттерн для RST 08h и RST 18h. Для IM1 (`RST 38h`) — аналогично, но
прерывание может прийти в любой момент, поэтому критические секции (своя
страница в W0) обязаны быть обёрнуты в `DI`/`EI` (или проверять вложенность
через счётчик), иначе повторное прерывание при уже замапленной странице
попадёт в стаб, а не в EXP.asm.
**Важно:** пока в W0 стоит ваша страница, системные вызовы (`RST 08h`/`10h`/`18h`)
проходят через стабы и работают корректно, но с накладными расходами
(~10+ тактов на каждый вызов). Код на странице не должен вызывать DSS/BIOS
напрямую — только через RST. При этом сам стаб не должен использовать
RST-инструкции, так как они адресуют W0, который в момент вызова стаба
всё ещё содержит вашу страницу, а не ПЗУ — сначала переключите ПЗУ через
`OUT (#7C),A`.
Перед первым маппингом своей страницы в W0 сохраните текущее значение порта
`#82` (`IN A,(#82)` — оно даёт номер системной/ПЗУ-страницы). Эта страница
понадобится стабам для восстановления после вызова во время прерываний,
когда ПЗУ уже может быть в W0, а стаб не успел его переключить.
Подтверждённая реализация: `tests/w0page` (см. `sprite-api-design.md §9в`).
---
## 4.3 Окно W1 (#4000..#7FFF) — приложение
W1 — основное окно для кода и данных приложения. По умолчанию содержит страницу 5 (RAM, инициализируется BIOS).
**Использование BIOS/DSS:**
BIOS и DSS могут **временно** переключать W1 на свою рабочую страницу. Это происходит:
- При файловых операциях DSS: чтении FAT, загрузке секторов — DSS маппит одну из страниц разделяемого пула в W1 (DIRPAGE, FATPAGE, TXTPAGE, DRVPAGE).
- При графических операциях BIOS: `PIC_FN2`/`PIC_FN3` (блоковые копии) могут переключать W1 на страницу видео-ОЗУ.
- **Важно:** перед возвратом из вызова BIOS/DSS **восстанавливает** предыдущую страницу W1. Типовой паттерн в исходниках DSS:
```asm
IN A,(SLOT1) ; сохранить текущую страницу W1
PUSH AF
... работа с разделяемым буфером ...
POP BC
LD C,SLOT1
OUT (C),B ; восстановить W1
```
Для вызывающей программы переключение W1 полностью прозрачно — после возврата из любой функции DSS/BIOS содержимое W1 идентично тому, что было до вызова.
**Практические рекомендации:**
- Размещайте код и данные в W1, рассчитывая, что он остаётся стабильным между вызовами.
- При длительных файловых операциях (последовательный `READ` больших объёмов) W1 может быть затронут только в моменты внутреннего обращения DSS к FAT/буферам, но не во время собственно чтения данных в буфер программы.
- Если нужно гарантированно сохранить данные через вызов DSS — используйте W2, который BIOS/DSS **никогда не трогают**.
---
## 4.4 Окно W2 (#8000..#BFFF) — стек (критично)
W2 — **единственное окно, которое BIOS и DSS гарантированно не трогают**. Поэтому стек ОБЯЗАН находиться здесь:
- **SP ≤ #BFFF** при вызовах DSS (`RST 10h`).
- **SP ≥ #8000** при вызовах BIOS (`RST 08h`).
- Пересечение → стек в `#8000..#BFFF`.
- По умолчанию SP инициализируется в `#BFFE` (значение из EXE-заголовка, §4.7).
**Почему W2 — единственное стабильное окно:**
| Окно | Может быть переключено BIOS/DSS | Почему |
|------|--------------------------------|--------|
| W0 | Да | DSS переключает на COREPAGE при `RST 10h`; BIOS переключает на ПЗУ |
| W1 | Да, временно | DSS маппит разделяемые буферы для FAT/директорий; BIOS при графических операциях — и **восстанавливает** перед возвратом |
| W2 | **Нет** | **Никогда. Стек должен быть доступен всегда.** |
| W3 | Да, временно | DSS маппит разделяемые буферы; BIOS при графике переключает на видео-ОЗУ; оба **восстанавливают** исходную страницу перед возвратом |
**EXEC и W2:** при загрузке новой программы (`EXEC`, функция 40h) DSS сохраняет страницу W2 в EXSTACK (вместе с W1 и W3). После завершения программы (`EXIT`, функция 41h) W2 восстанавливается.
**Практические рекомендации:**
- Размещайте стек строго в W2. Стандартное значение `SP = #BFFE` — начало стека с запасом ~14 КБ до `#8000`. Можно уменьшить, если программа неглубокая.
- W1 и W3 при вызовах DSS и BIOS **сохраняются и восстанавливаются** (см. §4.3, §4.5), но W2 — единственное окно, которое **никогда не трогается** ни DSS, ни BIOS.
- Если программе нужно больше памяти, чем даёт W2 (например, для хранения больших массивов), выделяйте блоки через DSS `GETMEM` (функция 3Dh) и мапьте их в W1 или W3.
---
## 4.5 Окно W3 (#C000..#FFFF) — разделяемые страницы
W3 — окно, в которое DSS временно маппит страницы своего разделяемого пула для дисковых буферов и служебных данных (адрес `#C000`):
| Страница | Назначение | Адрес в W3 |
|----------|------------|-------------|
| DIRPAGE (0) | Буфер директории | `#C000` |
| FATPAGE (1) | Кеш FAT | `#C000` |
| TXTPAGE / ENVPAGE (2) | Буфер строки/окружения | `#C000..` (PATH_PNT_ARRAY `#FC80`, ENVTEMP `#FE00`) |
| DRVPAGE (3) | Страница драйвера | `#C000` |
| COREPAGE (4) | Ядро DSS (маппится в W0 при `RST 10h`) | — |
**DSS СОХРАНЯЕТ и ВОССТАНАВЛИВАЕТ W3** после каждой операции. Типовой паттерн в исходниках:
```asm
IN A,(SLOT3) ; сохранить текущую страницу W3
PUSH AF
... работа с разделяемым буфером ...
POP BC
LD C,SLOT3
OUT (C),B ; восстановить W3
```
Это означает, что для вызывающей программы переключение W3 полностью прозрачно — после возврата из любой функции DSS содержимое W3 идентично тому, что было до вызова.
**Какие функции трогают W3:**
Функции, обращающиеся к файловой системе, временно маппят разделяемые страницы в W3: поиск файла (`F_FIRST`/`F_NEXT`), чтение FAT (`OPEN`, `READ`, `WRITE`), работа с каталогами (`CHDIR`, `MKDIR`), буферы драйвера диска. Функции без дисковой активности (`VERSION`, `CURDISK`, `WAITKEY`, `LOCATE`, `SYSTIME`, `ENVIRON` и т.п.) W3 не трогают вообще.
BIOS при графических операциях также временно переключает W3 на страницы видео-ОЗУ (например, `PIC_SET_PAL`, `PIC_FN2`) — и тоже восстанавливает.
**Практические рекомендации:**
- W3 можно использовать для данных программы — после возврата из любой функции DSS содержимое W3 не меняется.
- Единственное исключение — `EXEC` (функция 40h): она загружает новую программу, и W3 получает страницы загруженного образа. Это ожидаемое поведение.
- Если программа не вызывает дисковых и графических функций — W3 полностью стабилен.
- **Стек нельзя размещать в W3** — не потому что W3 «теряется», а потому что переключение W3 происходит ВО ВРЕМЯ вызова. Если `SP` указывает на W3, то в момент, когда DSS временно маппит свой буфер, все push/pop внутри DSS будут обращаться к буферной странице, а не к данным программы. Кроме того, при прерывании IM1 обработчик EXP.asm сам переключает W3 на SYS_PAGE — что также сломает стек, если он в W3.
---
## 4.6 Физическая память и страницы
Физическая память Sprinter — не сплошной массив RAM. Разные области адресного
пространства зарезервированы под видео-ОЗУ, ПЗУ и кеш. Номер страницы — это
значение, которое записывается в порт окна (`#82`/`#A2`/`#C2`/`#E2`);
8-битный порт даёт **256 значений** (`#00..#FF`).
**Фиксированная карта страниц:**
| Диапазон | Страниц | Назначение |
|----------|---------|------------|
| `#00..#4F` (0..79) | 80 (1,25 МБ) | RAM общего назначения |
| `#50..#5F` (80..95) | **16 (256 КБ)** | **Видео-ОЗУ (VRAM) — фиксировано в железе** |
| `#60..#DF` (96..223) | 128 (2 МБ) | RAM общего назначения (верхний диапазон) |
| `#E0..#EF` (224..239) | **16 (256 КБ)** | **ПЗУ: BIOS + EXP + ZX ROMs + bitstream — фиксировано** |
| `#F0..#FF` (240..255) | 16 (256 КБ) | **Fast RAM (кеш) / системные страницы** |
**Выделенные страницы:**
| Страница | Назначение |
|----------|------------|
| `4` | **COREPAGE** — ядро DSS (маппится в W0 при `RST 10h`) |
| `5` | Стандартная страница приложения W1 (по умолчанию) |
| `#41` (65) | **Spec_Page** — сохранение состояния при soft-reset [EXP.asm:859,1019] |
| `#FE` (254) | **SYS_PAGE** — системные данные BIOS (INT_ID, INT_ADRESS, буферы) [MAIN.asm:58] |
| `#FF` (255) | **Dummy-страница**: чтение → `#FF`, запись игнорируется |
**Ключевые моменты:**
- **VRAM (`#50..#5F`) фиксирована аппаратно**, независимо от объёма RAM. Эти
16 страниц не являются частью RAM SIMM — это отдельная микросхема 256 КБ.
Биты 2-3 номера страницы кодируют режим записи: `#50` normal, `#54` без
ОЗУ-тени, `#58` прозрачный (байт `#FF` не пишется), `#5C` оба режима.
- **ПЗУ (`#E0..#EF`)** — 256 КБ флеш-памяти с BIOS, EXP, ZX Spectrum 48/128 ROM,
TR-DOS, логотипом и битстримом для CPLD.
- **Fast RAM (`#F0..#FF`)** — до 512 КБ быстрого SRAM (0 wait-states),
доступна через порт `#FB`. `#FE` (SYS_PAGE) и `#FF` (dummy) — часть этого
диапазона. При включённом кеше порт `#FB` конфликтует с CBL-звуком.
- Для 2 МБ RAM: `#60..` заполнены до `#7F` (32 стр. = 512 КБ); остаток
`#80..#DF` — неиспользуемые страницы (отображаются как `#FF`).
- Для 4 МБ RAM: `#60..#DF` целиком заполнены RAM (128 стр. = 2 МБ).
> **Нумерация:** порты `#82`/`#A2`/`#C2`/`#E2` дают прямой доступ по
> физическому номеру (#00..#FF). Порты `#7FFD`/`#1FFD` (совместимость с
> ZX-Spectrum 128) используют сквозную нумерацию: номер в `#7FFD` (0..7)
> маппится на физические страницы видеоОЗУ `#50..#5F` младшими 3 битами;
> для работы с произвольными страницами используйте порты `#82`/`#A2`/`#C2`/`#E2`.
---
## 4.7 EXE-файл: формат и загрузка
Загрузка исполняемых файлов выполняется функцией DSS `EXEC` (40h). Формат EXE-файла:
**Заголовок (512 байт):**
| Смещение | Размер | Поле | Описание |
|----------|--------|------|----------|
| +0 | 3 | `EXE_EXT` | Сигнатура `"EXE"` |
| +3 | 1 | `VERSION` | Минимальная версия DSS (1 = v1.xx, 0 = без пути/аргументов) |
| +4 | 2 | `OFFCOD1` | Младшее слово смещения образа в файле |
| +6 | 2 | `OFFCOD2` | Старшее слово смещения (OFFCOD2 << 16 + OFFCOD1 = offset) |
| +8 | 2 | `LOADER` | Размер первичного загрузчика (0 = весь образ сразу) |
| +10 | 6 | `RESERVED` | Зарезервировано |
| +16 | 2 | `LD_ADDR` | Адрес загрузки (`#4100..#FFFF`, старший байт маскирован `#3F`) |
| +18 | 2 | `PC_REG` | Точка входа (entry point) |
| +20 | 2 | `SP_REG` | Начальный SP (по умолчанию `#BFFE`) |
| +22 | 1 | `UnUsedPoint` | Не используется |
| +23 | 489 | `RESERVED2` | Зарезервировано (обнуляется; используется как временный стек EXEC) |
**Процесс загрузки (EXEC, функция 40h):**
1. Открыть файл на чтение.
2. Считать 512-байтный заголовок в буфер ядра.
3. Проверить сигнатуру `"EXE"` и версию (≥ минимальной 1).
4. Если `LOADER=0`: seek на смещение `OFFCOD2:OFFCOD1`, считать весь образ по адресу `LD_ADDR`.
5. Если `LOADER>0`: считать только первичный загрузчик по адресу `LD_ADDR` (он дочитает остальные сегменты).
6. Выделить блок памяти через `GETMEM` (функция 3Dh), зарегистрировать в таблице RAM.
7. Сохранить текущие страницы окон (SLOT1-3) в EXSTACK.
8. Определить, сколько страниц нужно, и замаппить их через `SETWIN1`/`SETWIN2`/`SETWIN3`:
- Если `LD_ADDR ≥ #C000`: маппится только W3.
- Если `LD_ADDR ≥ #8000`: маппятся W2 и W3.
- Иначе: маппятся W1, W2 и W3.
9. Построить **PSP** (Program Segment Prefix) — блок данных сразу ниже `LD_ADDR`:
- `LD_ADDR-3`: файловый манипулятор (file handle).
- `LD_ADDR-2`: идентификатор блока памяти.
- `LD_ADDR-1`: номер задачи.
- `LD_ADDR+0`: размер CLP (командной строки).
- `LD_ADDR+1..+128`: CLP-буфер.
- После CLP: строка пути `"A:\DIR\FILE.EXE",0`.
10. Установить SP из заголовка (`SP_REG`), сохранить IX → адрес PSP.
11. Если `VERSION=0` — установить текущий каталог из пути PSP.
12. Затолкать в стек адрес возврата (RETFAR к DSS), **EI**, JP на точку входа.
**Multi-bank EXE:**
При наличии нескольких банков (`LOADER>0`) файл устроен так:
```
[512-байт заголовок] [HOME-сегмент] [Банк 1 (16 КБ)] [Банк 2 (16 КБ)] ...
```
- HOME-сегмент: образ, загружаемый DSS по `LD_ADDR`.
- Банки: 16-КБ блоки, каждый на своей физической странице.
- `LOADER` = размер HOME-сегмента (DSS загружает только его; первичный загрузчик сам дочитывает банки через `READ` из оставшегося открытым файла).
---
## 4.8 Прерывания и память
**Обработчик IM1 (`RST 38h`):**
Вектор фиксирован на `#0038` (W0). Реализация в EXP.asm:
1. Переключает W3 на SYS_PAGE (BIOS).
2. Проверяет флаг `INT_ID` = `#AA` (установлен при инициализации BIOS).
3. Если флаг совпадает — вызывает пользовательский обработчик, адрес которого хранится в SYS_PAGE (`INT_ADDRESS`, `INT_PAGE`).
4. После возврата восстанавливает W3 и выполняет `EI; RETI`.
Это позволяет пользовательским программам устанавливать свои обработчики INT без полного перехвата вектора.
**Обработчик IM1 (системный):**
При возникновении прерывания Z80 исполняет `RST 38h` → вызов по адресу `#0038`. Реализация в EXP.asm (всегда доступна, т.к. `#0038` в W0):
1. Переключает W3 на SYS_PAGE (BIOS).
2. Проверяет флаг `INT_ID` = `#AA` (установлен при инициализации BIOS).
3. Если флаг совпадает — вызывает пользовательский обработчик, адрес и страница которого хранятся в SYS_PAGE (`INT_ADDRESS`, `INT_PAGE`). Это позволяет программам устанавливать свои ISR без полного перехвата вектора.
4. После возврата восстанавливает W3 и выполняет `EI; RETI`.
**Обработчик IM2 (опционально, для продвинутых программ):**
Sprinter по умолчанию не использует IM2. Исходники содержат шаблон `IM2_INT.asm` для программ, желающих переключиться на IM2:
1. Сохраняет AF, BC, DE, HL, IX, IY и теневые регистры.
2. Включает ПЗУ (`OUT (#7C),A`) — KEYSCAN требует ПЗУ в W0.
3. Вызывает KEYSCAN (опрос AT-клавиатуры).
4. Восстанавливает все регистры, `EI`, `RETI`.
При переходе на IM2 программа сама выбирает значение `I` и размещает таблицу векторов в соответствующем диапазоне памяти.
**Рекомендации:**
- Если программа не использует прерывания — держите W0 в ПЗУ.
- Если программа использует IM1 — можно переопределить обработчик через структуру в SYS_PAGE (флаг `INT_ID`, поля `INT_ADDRESS`/`INT_PAGE`).
- Если программа использует IM2 — переключение полностью на ответственности программы; ядро Sprinter не предоставляет стандартного расположения таблицы векторов.
---
## 4.9 Типовые грабли
- **Данные читаются как `#FF` / не сохраняются** — код или данные попали в незамапленное окно (страница `#FF`). Причина: образ программы меньше 16 КБ, DSS выделил только одну страницу, а код/данные распределены линкером в незамапленные окна. Лечение: убедитесь, что код и данные укладываются в одно окно, или явно выделяйте и мапьте остальные окна через `GETMEM`/`SETWIN1`-`SETWIN3`.
- **Крэш при вызове RST при нестандартном W0** — если перед вызовом W0 содержал произвольную страницу ОЗУ, DSS при `RST 10h` переключит его, но может не восстановить корректно. Решение: перед вызовом RST включите ПЗУ (`OUT (#7C),A`), если не уверены в состоянии W0.
- **Стек в W3 (не-рабочий вариант)** — если стек размещён в W3, при любом дисковом вызове W3 будет переключён на разделяемый буфер DSS, и стековые данные будут забиты. Результат: мгновенное зависание при возврате из функции. Единственное рабочее место для стека — W2.
- **CBL не работает при включённом кеше** — порт `#FB` используется как кеш-память (Fast RAM) и как порт CBL-звука. Если кеш включён, запись в `#FB` уходит в кеш, а не в CBL. Детали: `11-ports.md`, `12-bugs.md`.
- **Прерывание уходит не туда при IM2** — если программа переключилась в IM2 с неинициализированной таблицей векторов, любое прерывание приведёт к исполнению мусора. Решение: используйте IM1 (режим по умолчанию) или инициализируйте таблицу перед переключением.
- **Не путать W3 с «теряется после вызова»** — многие источники утверждают, что DSS сбрасывает W3. На самом деле DSS **сохраняет и восстанавливает** W3 после каждой операции. Стек всё равно нельзя размещать в W3 (см. §4.5), но для данных W3 полностью стабилен между вызовами. Если ваша программа полагается на старый миф — проверьте код.
---
## 4.10 Кросс-ссылки
- Порты страниц и детали регистров: `11-ports.md`
- BIOS (графика, окна): `02-bios.md`
- DSS/ESTEX (EXEC, GETMEM, SETWIN): `03-dss.md`
- Прерывания: `07-irq.md`
- Баги (CBL vs кеш, порт #FB): `12-bugs.md`
- EXE-формат и PSР: `memory/exe-format.md`
+279
View File
@@ -0,0 +1,279 @@
# 5. Графика и видеосистема
## 5.1 Общие сведения
Видеосистема Sprinter Sp2000 реализована в ППЛМ. Видео-ОЗУ — 256 КБ
(512 КБ на некоторых платах [Architecture.txt:17]).
| Параметр | Значение |
|---|---|
| VRAM | 256 КБ, независимая микросхема (не часть RAM SIMM) |
| Физические страницы | `#50..#5F` (16 страниц × 16 КБ), фиксировано в железе |
| Видеорежимы | ZX-40, ZX-80, GR-256-8, GR-16-16 |
| Палитра | 8 палитр × 256 цветов из 16 млн (RGB, BGR) |
| Видеовыход | CGA-монитор, RGB, TV (SCART) |
**Теневая память (Shadow RAM):** Видео-ОЗУ Спринтера является теневой памятью
(§5.4). Программа управляет режимом записи через номер подключаемой страницы
(§5.3): банк `0x50` пишет одновременно в VRAM и в основное ОЗУ; банк `0x54`
пишет только в VRAM, не трогая ОЗУ. На экране **всегда** отображается
содержимое VRAM, независимо от режима записи [IvanMak.txt:317322].
---
## 5.2 Режимы адресации видео-ОЗУ
Видео-ОЗУ может адресоваться двумя способами — **спектрумовским** и
**графическим** [IvanMak.txt:296300]. Спектрумовский режим используется для
Spectrum-совместимого вывода и загрузки знакогенераторов; графический — для
всех остальных видеорежимов (320×256×256, 640×256×16, текст 80×32).
### 5.2.1 Спектрумовский режим адресации
В этом режиме VRAM разбивается на **32 блока по 8 КБ**. Адрес блока задаётся
через порт `RGADR` (`PORT_Y`, `#89`):
| Бит | Назначение |
|-----|-----------|
| 4..0 | Номер блока (0..31) |
| 5 | Не используется |
| 6 | Запрет вывода (1 = отключить спектрумовский вывод) |
| 7 | Разрешение 16-КБ страницы (1 = два блока в `#4000..#7FFF`) |
Бит 0 RGADR вместе с битом 1 порта `#7FFD` определяет чётность подключаемого
блока VRAM: чётный RGADR → блок N в `#4000..#5FFF`, блок N+1 в `#C000..#DFFF`;
нечётный → наоборот [IvanMak.txt:333339].
Для записи в определённую пару спектрумовских блоков:
1. Записать номер блока в `RGADR` (`OUT (#89),A`).
2. Убедиться, что в окне (`#4000` или `#C000`) стоит подходящая страница
основного ОЗУ (любая, кроме `#50..#5F`).
3. Чтение/запись по спектрумовским адресам (`#4000..#5FFF`,
`#C000..#DFFF`) попадает в VRAM через текущий блок.
**Выключение спектрумовского вывода:** Если программа не использует
Spectrum-совместимую графику, `RGADR` рекомендуется установить в `#C0..#FF`
(бит 6 = 1). В этом случае вывод в VRAM через спектрумовский режим не
производится [IvanMak.txt:346348].
### 5.2.2 Графический режим адресации
В графическом режиме видео-ОЗУ организовано как матрица **256 строк
(rows) × 1024 байта** [IvanMak.txt:357358]. Адресация:
```
VRAM_адрес = (RGADR << 10) | (Z80_A[9:0])
где RGADR[7:0] = Y (номер строки, 0..255)
Z80_A[9:0] = X (смещение в строке, 0..1023)
```
Страницы `#50..#5F` при маппинге в любое окно Z80 включают графическую
адресацию. При этом младшие 4 бита номера страницы не влияют на адрес
памяти, а задают подрежим вывода (§5.3).
**Важная особенность:** Z80-адрес внутри окна (биты A[13:10] для окна W3)
**игнорируется** — весь 16-КБ диапазон окна отображает **одну и ту же
строку**, выбранную `RGADR`. Доступ к другим строкам — только через смену
`RGADR`. Это означает, что утверждение из SprinterGraphics programming.txt
о доступе к строкам 18..31 через адреса `#C400..#FC00` **неверно**.
**Структура 1-КБ строки (1024 байта):**
| Смещение в строке | Назначение |
|-------------------|-----------|
| `#000..#13F` (0..319) | Экранная страница 0 (первые 320 байт) |
| `#140..#27F` (320..639) | Экранная страница 1 (следующие 320 байт) |
| `#280..#3FF` (640..1023) | Не используется / данные палитры |
Две экранные страницы (0 и 1) переключаются битом 0 порта `RGMOD` [IvanMak.txt:409].
**Работа с адресацией из программы:**
```asm
; Установить страницу VRAM (0x50) в W3
IN A, (#E2) ; сохранить текущую W3
LD (old_page), A
LD A, #50
OUT (#E2), A ; W3 = VRAM страница 0x50
; Выбрать строку Y
LD A, Y ; Y = 0..255
OUT (#89), A ; RGADR = Y
; Чтение/запись пикселя (X,Y) для экранной страницы 0
LD HL, #C000 + X ; X = 0..319
LD (HL), colour ; запись байта пикселя
; Восстановить страницу
LD A, (old_page)
OUT (#E2), A
```
---
## 5.3 Подрежимы вывода (страницы `#50..#5F`)
Биты 2 и 3 номера страницы (из диапазона `#50..#5F`) задают подрежимы вывода
[IvanMak.txt:364386]:
| Бит | Страницы | Эффект |
|-----|----------|--------|
| 3 | `#58..#5F` | **Прозрачный цвет** — запись байта `#FF` игнорируется (для спрайтов) |
| 2 | `#54..#57`, `#5C..#5F` | **Без тени** — запись только в VRAM, не в основное ОЗУ (для курсора мыши) |
Биты 0 и 1 должны быть 0 для совместимости с будущими прошивками
[IvanMak.txt:384386].
---
## 5.4 Теневая память (Shadow RAM) и двойная запись
Видео-ОЗУ Спринтера является теневой памятью по отношению к основному ОЗУ
[IvanMak.txt:317322]. Видеоконтроллер **всегда** читает изображение из VRAM
для вывода на экран; основное ОЗУ в формировании изображения не участвует.
Однако при записи через графические банки `#50..#5F` поведение зависит от
выбранного подрежима:
- **Банк `0x50` (normal):** запись идёт **одновременно** в VRAM и в
соответствующую область основного ОЗУ. Чтение из того же Z80-адреса
возвращает данные из основного ОЗУ, а не из VRAM — VRAM для процессора
на чтение не видна.
- **Банк `0x54` (noshadow):** запись — только в VRAM, основное ОЗУ не
изменяется. Чтение — из основного ОЗУ (там хранится «фон»).
- **Банк `0x58` (transparent):** как normal, но байт `#FF` не пишется.
- **Банк `0x5C` (sprite):** transparent + noshadow.
- **Обычные банки (не `#50..#5F`):** никакого доступа к VRAM нет,
чтение/запись идут напрямую в основное ОЗУ.
**Зачем это нужно:**
1. **Восстановление после переключения видеорежима:** данные в основном ОЗУ
остаются нетронутыми, так что при возврате в графический режим экран можно
восстановить без перерисовки [IvanMak.txt:325].
2. **Heal спрайтов** (libbgi): фон, на который был выведен спрайт в банке
`GFX_BANK_SPRITE` (`0x5C`, без тени), остался в основном ОЗУ. После
перемещения спрайта фон копируется из основного ОЗУ обратно в VRAM
(операция heal), стирая спрайт.
3. **Курсор мыши** без сохранения/восстановления: временный вывод через
`GFX_BANK_NOSHADOW` (`0x5C`) — в основном ОЗУ данные под курсором не
портятся, достаточно при следующем обновлении просто перерисовать фон.
**Режимы записи (см. §5.3):**
| Режим | Пишет в VRAM | Пишет в осн. ОЗУ | Применение |
|-------|:---:|:---:|-----------|
| `0x50` (normal) | Да | Да | Обычный вывод |
| `0x54` (noshadow) | Да | **Нет** | временные эффекты |
| `0x58` (transparent) | Да (кроме `#FF`) | Да | Спрайты с прозрачностью без heal |
| `0x5C` (sprite) | Да (кроме `#FF`) | **Нет** | Курсор, спрайты (прозрачность + heal) |
---
## 5.5 Видеорежимы
Система поддерживает независимую установку режима для каждого квадратика
8×8 (или 16×8 для 640-режимов) на экране [IvanMak.txt:405469].
### Режимы вывода
| Режим | Разрешение | Цветов | Описание |
|-------|-----------|--------|----------|
| ZX-40 | 320×256 | 16 | Текстовый 40×32, Spectrum-совместимый |
| ZX-80 | 640×256 | 16 | Текстовый 80×32, до 36 знакогенераторов |
| GR-256-8 | 320×256 | 256 | Графический, 1 байт/пиксель |
| GR-16-16 | 640×256 | 16 | Графический, 4 бита/пиксель |
### Формат пикселей
**GR-256-8 (320×256×256):** один байт = один пиксель. Строка содержит
320 байт для экранной страницы 0 (смещения `#000..#13F`) и 320 байт для
страницы 1 (`#140..#27F`). Полный экран: 320 × 256 = 81 920 байт на страницу.
**GR-16-16 (640×256×16):** один байт = два пикселя: младший полубайт —
левый/первый пиксель, старший — правый/второй. Строка содержит 320 байт на
экранную страницу, что даёт 640 пикселей (320 × 2). Структура строки та же:
320 байт на страницу 0, 320 на страницу 1.
### Установка режима
Режим задаётся 4 байтами `Mode0..Mode3` в области режимов VRAM
(`#0300..#039F`). Полное описание формата Modei — [IvanMak.txt:448469].
Для переключения видеорежима из программ используется функция DSS `SETVMOD`
(50h) [DiskSyscalls.txt:345349]:
| A (режим) | Описание |
|-----------|----------|
| 02h | Текстовый 40×32×16 |
| 03h | Текстовый 80×32×16 |
| 81h | Графический 320×256×256 |
| 82h | Графический 640×256×16 |
B — страница экрана (0/1). Вызов: `RST 10h` с C=50h. Флаг C = ошибка
(например, режим не поддерживается данным монитором).
---
## 5.6 Палитра
8 палитр по 256 цветов, каждая занимает 1 КБ в VRAM по адресам
`#03E0..#03FE` [IvanMak.txt:540568].
| Адреса | Палитра | Назначение |
|--------|---------|-----------|
| `#03E0..#03E2` | Граф. 0 | Графические режимы |
| `#03E4..#03E6` | Граф. 1 | —//— |
| `#03E8..#03EA` | Граф. 2 | —//— |
| `#03EC..#03EE` | Граф. 3 | —//— |
| `#03F0..#03F2` | Текст. 4 | Цвет бумаги |
| `#03F4..#03F6` | Текст. 5 | Цвет символа |
| `#03F8..#03FA` | Текст. 6 | Цвет мерцания бумаги |
| `#03FC..#03FE` | Текст. 7 | Цвет мерцания символа |
Каждый четвёртый байт в каждой палитре не используется — не может попасть
на ЦАП [IvanMak.txt:545547]. Выбор палитры для каждого квадратика — через
биты 7..6 байта Mode1 [BIOS_v3.txt:348].
> **Примечание:** Architecture.txt упоминает всего 5 палитр (4 графические +
> 1 текстовая), но IvanMak описывает все 8: 4 графические (0–3) и 4 текстовые
> (4–7: бумага, символ, мерцание бумаги, мерцание символа)
> [IvanMak.txt:547548, 561568].
---
## 5.7 Структура VRAM в терминах BIOS
При использовании BIOS распределение VRAM [IvanMak.txt:387404]:
| Адреса (линии) | Назначение |
|----------------|-----------|
| `#0000..#003F` | Спектрумовский экран (блоки 0..1) |
| `#0040..#017F` | Первый графический экран (блоки 2..11) |
| `#0180..#02BF` | Второй графический экран (блоки 12..21) |
| `#02C0..#02FF` | Знакогенераторы текстового режима |
| `#0300..#039F` | Область описания режимов экрана |
| `#03A0..#03DF` | Зарезервировано |
| `#03E0..#03FF` | Палитра |
---
## 5.8 Заблуждения и неподтверждённое
**Миф о строках 18–31:** В документе SprinterGraphics programming.txt
утверждается, что после выбора строки Y через RGADR, по адресам
`#C400..#FC00` доступны строки Y+1..Y+15 (с шагом 1 КБ). В эмуляторе MAME
это **не работает** — весь 16-КБ диапазон страницы `#50..#5F` отображает
только строку Y, независимо от бит A[13:10] адреса Z80. Требуется проверка
на реальном Sprinter Sp2000.
---
## 5.9 Кросс-ссылки
- Палитра и видеорежимы BIOS: `02-bios.md §2.7`
- SETVMOD (DSS): `03-dss.md §3.10`
- Акселератор: `06-accel.md`
- Быстрое ОЗУ и конфликт с акселератором: `04-memory.md §4.6`
- Порты RGADR/RGMOD: `11-ports.md`
- Баги акселератора: `12-bugs.md`
- Проектные документы: `sprite-api-design.md`, `accel-fill-budget.md`
+221
View File
@@ -0,0 +1,221 @@
# 6. Акселератор
## 6.1 Общие сведения
Акселератор — внутреннее устройство, реализованное в ППЛМ. Присутствует почти
во всех конфигурациях Sprinter (Sprinter-2 и выше). Предназначен для ускорения
пересылки блоков данных между RAM и VRAM (до физического предела скорости ОЗУ).
Не поддерживает ПЗУ и Fast RAM [accelerator_doc.txt:5].
**Возможности:**
- Быстрая заливка горизонтальной или вертикальной линии экрана (1..256 пикселей)
одним цветом; в режиме 640×256 — линия 1..512 пикселей.
- Быстрое копирование горизонтальной или вертикальной линии (1..256 пикселей).
- Логические операции AND, OR, XOR с блоками данных.
**Внутренняя память:** 256 байт в ППЛМ. Данные загружаются в эту память,
затем копируются в выбранный участок RAM/VRAM. Операция копирования может
повторяться многократно — это позволяет заполнять экран текстурой без
перезагрузки данных.
**Автоматический инкремент адреса:**
- **Горизонтальные** операции (`LD L,L` — копирование, `LD C,C` — заливка):
акселератор автоматически инкрементирует регистр HL (адрес приёмника) на
каждом шаге. Для копирования источник (DE) также инкрементируется.
- **Вертикальные** операции (`LD A,A` — копирование, `LD E,E` — заливка):
акселератор автоматически меняет порт `RGADR` (`#89`) — координату Y.
После завершения операции `RGADR` содержит новое значение
(Y + размер_блока, если не было переполнения).
- **Комбинированный** режим (горизонтальный источник + вертикальный приёмник):
сначала загружается байт из последовательных данных, затем выполняется
вертикальная запись. `RGADR` инкрементируется; регистр-источник (DE)
тоже инкрементируется — можно последовательно читать спрайт и рисовать
его вертикальными строками (§6.3, пример «Спрайт»).
## 6.2 Команды
Акселератор управляется специальными инструкциями Z80, которые выполняются
как NOP обычным процессором, но распознаются ППЛМ [IvanMak.txt:810848].
| Инструкция | Код | Действие |
|-----------|:---:|----------|
| `LD B,B` | `#40` | **Выключить** акселератор |
| `LD D,D` | `#52` | **Включить** + режим «задать размер блока» |
| `LD A,size` | `#3E size` | Установить размер блока (сразу после `LD D,D`) |
| `LD C,C` | `#4F` | **Горизонтальная заливка** одним байтом |
| `LD E,E` | `#5B` | **Вертикальная заливка** одним байтом |
| `LD L,L` | `#6D` | **Горизонтальное копирование** блока |
| `LD A,A` | `#7F` | **Вертикальное копирование** блока |
| `LD H,H` | `#64` | Зарезервировано |
**Логические операции** (AND, OR, XOR) выполняются командами
`AND (HL)`, `OR (HL)`, `XOR (HL)` после загрузки блока в память акселератора.
## 6.3 Примеры
### Копирование экрана (страница 0 → страница 1)
```asm
; страница VRAM уже открыта по адресу #C000
LD HL, #C000 ; адрес начала линии первого экрана
LD DE, #C140 ; адрес начала линии второго экрана
LD BC, #140 ; длина строки по горизонтали (320)
DI ; запретить прерывания
LD D,D ; включить акселератор: режим размера блока
LD A, 0 ; размер блока = 256 байт (0 = 256)
LD A,A ; вертикальное копирование
LDIR ; копировать
LD B,B ; выключить акселератор
EI ; разрешить прерывания
```
Время исполнения: ~1.2 прерывания (~24–26 мс на полный экран)
[IvanMak.txt:822823], [accelerator_doc.txt:37].
### Заливка блока одним цветом
```asm
LD DE, #C000 + X ; X — смещение по горизонтали
LD A, Y
OUT (#89), A ; RGADR = Y
DI
LD D,D ; режим размера блока
LD A, width ; ширина блока
LD B,B ; стоп
LD A, colour ; цвет заливки
LD C,C ; горизонтальная заливка
LD (DE), A ; залить
LD B,B ; стоп
EI
```
### XOR блока данных
```asm
LD HL, addr_1
LD DE, xor_data
DI
LD D,D
LD A, 0 ; 256 байт
LD L,L ; горизонтальное копирование
LD A, (DE) ; загрузить блок в память акселератора
XOR (HL) ; XOR с данными в RAM
LD (HL), A ; сохранить результат
LD B,B ; стоп
EI
```
### Копирование вертикальной линии с разделением load/write
Загрузка байта в буфер акселератора и запись из буфера в VRAM разнесены —
между ними можно выполнять произвольный код (например, сменить Y).
Буфер акселератора и размер блока **сохраняются** между включениями и
выключениями (`LD B,B`), что позволяет гибко разделять чтение
и запись.
```asm
LD HL, #C000 + X1 ; источник
LD DE, #C000 + X2 ; приёмник
DI
LD D,D ; акселератор вкл, режим размера
LD A, size ; размер блока
LD B,B ; выключить (размер запомнен)
LD A, Y1
OUT (#89), A ; RGADR = Y1
LD A,A ; вертикальное копирование
LD A, (HL) ; загрузить байт из (HL) в буфер акселератора
LD B,B ; выключить (буфер запомнен)
LD A, Y2
OUT (#89), A ; RGADR = Y2 (смена Y между load и write)
LD A,A ; вертикальное копирование
LD (DE), A ; записать буфер в (DE) — акселератор пишет
; вертикальную линию size пикселей
LD B,B ; выключить
EI
```
### Спрайт вертикальными строками из последовательных данных
Спрайт хранится как последовательность байт вертикальных линий. Для каждой
колонки спрайта: загружается байт из последовательных данных, затем
акселератор пишет его вертикально (автоинкремент `RGADR`). После завершения
колонки — сдвиг по X (`INC HL`) и повтор.
```asm
LD DE, sprite_data ; DE → последовательные данные спрайта
LD HL, #C000 + X_COORD ; HL → начальная позиция на экране
LD B, sprite_width ; ширина спрайта в колонках
LD D,D ; акселератор вкл, режим размера
LD A, sprite_height ; высота одной вертикальной линии
LD B,B ; акселератор выключили (размер акселератор запомнил)
row:
LD A, Y_COORD
OUT (#89), A ; RGADR = стартовая Y
DI
LD L,L ; режим «загрузить из последовательных данных»
LD A, (DE) ; загрузить байт спрайта в буфер акселератора
LD A,A ; вертикальное копирование
LD (HL), A ; записать — акселератор копирует байт
; вертикально, RGADR++ на каждом шаге
LD B,B ; выключить акселератор
EI
INC HL ; следующая колонка по X
DJNZ row
```
## 6.4 Скорость
Время работы акселератора ограничено только физической скоростью ОЗУ
[IvanMak.txt:840843]:
```
Время ≈ время команды без акселератора + число_байт / 7 000 000 (сек)
```
## 6.5 Комбинированный режим: общая схема
Общий паттерн для работы с акселератором в комбинированном режиме
(горизонтальный источник + вертикальный приёмник):
```
LD D,D ; 1. Включить акселератор, режим размера
LD A, size ; 2. Задать размер блока (1..256)
LD L,L ; 3. Режим «горизонтальный источник»
<чтение байта> ; 4. Загрузить байт в буфер акселератора (LD A,(DE) и т.п.)
LD A,A ; 5. Режим «вертикальный приёмник»
<запись байта> ; 6. Записать — акселератор делает вертикальное копирование
; с автоинкрементом RGADR и приёмника
LD B,B ; 7. Выключить акселератор
```
После шага 6 `RGADR` содержит Y + size, HL — X + 1 (если был `INC HL`).
## 6.6 Прерывания
**Старая прошивка:** DI обязателен на время работы акселератора — система
команд Z80 сильно меняется, и ISR не сможет корректно выполниться
[IvanMak.txt:844846].
**Новая прошивка:** акселератор может работать с EI. По приходу прерывания
он отключается, а по `RETI` включается обратно [IvanMak.txt:846848].
Использовать этот режим следует с осторожностью — без проверки на реальном
железе неизвестно, какая версия прошивки установлена.
---
## 6.7 Кросс-ссылки
- Баги акселератора: `12-bugs.md`
- Использование акселератора в libbgi: `sprite-api-design.md`
- Ограничение скорости: `accel-fill-budget.md`
- Fast RAM и конфликт с портом CBL: `04-memory.md §4.6`
+92
View File
@@ -0,0 +1,92 @@
# 7. Прерывания (IRQ)
## 7.1 Общие сведения
Sprinter Sp2000 поддерживает режимы прерываний IM1 и IM2.
### Источники прерываний
| Источник | Происхождение | Описание |
|----------|---------------|----------|
| Кадровые (INT) | Видеоконтроллер | Квадратик экрана в 8-й линии |
| CTC | Z84C15 | Таймерные прерывания |
| SIO | Z84C15 | AT-клавиатура |
| PIO | Z84C15 (бит 0 порта B, `#1F`) | ISA-слоты |
| CBL | COVOX-Blaster | Запрос данных |
### Различение источников
Кадровые и клавиатурные прерывания приходят с вектором `0FFh` [Parinov.txt:956]. Способ различения:
- Клавиатура выставляет бит 0 в порте `19h` — признак приёма байта [Parinov.txt:1049]
- Если бит 0 порта `19h` не установлен — прерывание кадровое
> **Примечание:** [Parinov.txt:956] описывает альтернативный метод — «отличать по биту приема байта в порте клавиатуры». Оба метода сводятся к проверке бита 0 в порте `19h`.
### Выбор режима прерываний (IM1 vs IM2)
| Режим | Контекст | Источник |
|-------|----------|----------|
| IM2 | Рекомендован для работы с BIOS через RST | IvanMak.txt:14691473 |
| IM1 | Используется DSS для своих обработчиков | — |
| IM1 | Fast-RAM секция предполагает обработчик по `#0038` | Parinov.txt:601 |
| IM2 | Рекомендован при выставленных прерываниях от CBL | Parinov.txt:762763 |
> **Примечание:** разные подсистемы рекомендуют разные режимы. IM2 предпочтителен при интенсивной работе с прерываниями (CBL, BIOS). IM1 используется DSS по умолчанию.
## 7.2 Кадровые прерывания (INT)
Генерируются в момент прохождения 8-й строки квадратика (настраивается через бит 0 порта `#FE`). Положение INT подстраивается под стандарт Pentagon (320 строк) или Scorpion (312 строк).
> **Мышь — есть ли аппаратные прерывания?** [Parinov.txt:956] утверждает: «От мыши прерывания не приходят. Сделать можно, но сейчас их нет.» [ProgrammerManual.txt:17841786] описывает драйвер мыши, где «каждое нажатие и отпускание клавиш или перемещение мыши вызывает прерывание», и документирует функцию «80h MOUSE HARDWARE INTERRUPT» [ProgrammerManual.txt:19301937]. Parinov (разработчик драйвера) отрицает их наличие — возможно, ProgrammerManual описывает планируемую функциональность.
## 7.3 CTC (Z84C15)
Таймер CTC используется для:
- Генерации периодических прерываний
- Задания частоты CBL (COVOX-Blaster)
- Синхронизации звука
## 7.4 SIO (Z84C15)
Внутренний SIO Z84C15 обслуживает:
- AT-клавиатуру (порты `#18`/`#19`)
- RS-232 (для мыши и других последовательных устройств)
## 7.5 IM1 — вектор #0038
Стандартный Z80-режим. Все INT ведут на адрес `#0038`. Используется DSS для своих обработчиков.
## 7.6 IM2 — таблица векторов
IM2 использует таблицу векторов (256 байт) в фиксированной странице (обычно W3). Вектор прерывания — `0FFh`.
Для работы IM2 требуется:
1. Таблица векторов в резиденте (W3)
2. Регистр I = старший байт адреса таблицы
3. Каждый вектор — адрес обработчика (2 байта)
## 7.7 Акселератор и прерывания
При работе акселератора система команд Z80 сильно меняется.
| Режим | Описание | Источник |
|-------|----------|----------|
| Старый | DI обязателен | IvanMak.txt:844846, Parinov.txt:954 |
| Новый | EI/RETI: по INT акселератор отключается, по RETI включается | IvanMak.txt:846848 |
> **Примечание:** IvanMak описывает два режима (старый — DI, новая прошивка — EI/RETI). Parinov знает только старый. Без тестирования на железе неизвестно, какая версия прошивки установлена.
## 7.8 Цепочка обработчиков
TODO (проектный `im2_isr_design.md`): цепочка irq_install/remove, FPS-делитель, обработчики клавиатуры.
## 7.9 Кросс-ссылки
- Порты прерываний: `11-ports.md`
- Проектный документ по IM2/ISR: `im2_isr_design.md`
- Клавиатура: `09-input.md §9.1`
- CBL (прерывания): `10-sound.md §9.4.6`
+197
View File
@@ -0,0 +1,197 @@
# 8. Ввод-вывод
## 8.1 Клавиатура и мышь
Клавиатура и мышь вынесены в отдельный документ: `09-input.md`.
Сводная информация по портам и прерываниям — `09-input.md §§9.19.6`.
## 8.2 Звук
Звуковая подсистема (AY-3-8910, COVOX, COVOX-Blaster, бипер) вынесена
в отдельный документ: `10-sound.md`.
Сводная карта звуковых портов: `11-ports.md §11.6`.
## 8.3 Дисковая подсистема
### 8.3.1 FDD (WD1793 / КР1818ВГ93)
Контроллер дисковода — стандартный Beta Disk Interface (WD1793).
Аппаратное перенаправление `#1F → #0F` (см. §8.4).
**Регистры (по данным MAME sprinter.cpp):**
| Z80-адрес | Регистр |
|-----------|---------|
| `#0F` | Status (чт) / Command (зп) |
| `#1F` | —//— (аппаратный ремап на `#0F`) |
| `#3F` | Track |
| `#5F` | Sector |
| `#7F` | Data |
| `#FF` | Drive Control (выбор дисковода) |
**Противоречие: адресация регистров ВГ93.**
> - [IvanMak.txt:12701274]: 4 регистра + `#FF` — управление дисководом.
> - [Parinov.txt:963]: 3 регистра (data-регистр `#7F` пропущен).
>
> MAME реализует полный набор по IvanMak: Status/Command (`#0F`), Track (`#3F`),
> Sector (`#5F`), Data (`#7F`). Data-регистр важен — через него читаются/пишутся
> сектора. Parinov, вероятно, упустил его.
**Turbo-режим FDD (DCP 0x16/0x17, биты):**
- Бит 0 = скорость (0=норм, 1=турбо)
- Бит 1 = отключение FDD [sprinter.cpp:914920]
### 8.3.2 HDD (IDE/ATA)
Два ATA-интерфейса: первичный и вторичный. Доступ — через DCP-декодер
(внутренние коды 0x20–0x2b).
**Регистры ATA CS0 (по sprinter.cpp):**
| DCP код | Z80-адрес (старш.) | Регистр |
|---------|-------------------|---------|
| 0x20 | `#xx50` | Data (16-bit через защёлку байтов) |
| 0x21 | `#xx51` | Error (чт) / Features (зп) |
| 0x22 | `#xx52` | Sector Count |
| 0x23 | `#xx53` | Sector Number |
| 0x24 | `#xx54` | Cylinder Low |
| 0x25 | `#xx55` | Cylinder High |
| 0x26 | `#xx56` | Device/Head |
| 0x27 | `#xx57` | Status (чт) / Command (зп) |
| 0x28 | `#xx58` | Alt Status (чт) / Device Control (зп) (CS1) |
| 0x2a | HDD1 select (вторичный) |
| 0x2b | HDD2 select (первичный) |
**16-битный data-порт:** MAME реализует защёлку байтов: чтение `#xx50`
возвращает младший байт (bit8=0), затем старший (bit8=1) [sprinter.cpp:958972].
**Противоречие: HDD — разные адреса для чтения и записи.**
> - [IvanMak.txt:1366]: кратко: «#xx50..#xx55 порты HDD»
> - [Parinov.txt:958959]: таблица с разными адресами для R/W:
> `0050h` (чт) / `0150h` (зп) — data, `4053h` (чт) / `4153h` (зп) — status/command.
>
> MAME использует единую карту DCP-кодов (0x20–0x28) без разделения R/W.
> Parinov описывает реальное поведение на Sprinter-2, где адресная линия A8
> управляет направлением — это внешняя особенность конкретной ППЛМ-прошивки.
> IvanMak упрощает до базовых DCP-кодов.
Доступ к IDE открыт в режиме Sprinter-ZX, в режимах Pentagon/Scorpion — закрыт.
### 8.3.3 CMOS (Dallas DS12887A)
Микросхема DS12887A (RTC + 114 байт NV RAM). Совместима с MC146818A.
Доступ через DCP-коды 0x1c0x1e.
| Z80-адрес | DCP код | Функция |
|-----------|---------|---------|
| `#DFBD` | 0x1d | Запись адреса регистра CMOS |
| `#FFBD` | 0x1c | Чтение данных из выбранного регистра |
| `#BFBD` | 0x1e | Запись данных в выбранный регистр |
При отсутствии микросхемы CMOS эмулируется BIOS [BIOS_v3.txt:308310].
**Карта регистров RTC (совместимо с MC146818A):**
| Рег. | Назначение | Формат |
|------|-----------|--------|
| 0x00 | Секунды | BCD |
| 0x01 | Секунды (будильник) | BCD |
| 0x02 | Минуты | BCD |
| 0x03 | Минуты (будильник) | BCD |
| 0x04 | Часы | BCD |
| 0x05 | Часы (будильник) | BCD |
| 0x06 | День недели (1=воскресенье) | BCD |
| 0x07 | Число | BCD |
| 0x08 | Месяц | BCD |
| 0x09 | Год | BCD |
| 0x0a | Регистр A (UIP, divider, rate) | биты |
| 0x0b | Регистр B (SET, PIE, AIE, UIE, SQWE, DM, 24/12, DSE) | биты |
| 0x0c | Регистр C (IRQF, PF, AF, UF) | биты (чт) |
| 0x0d | Регистр D (VRT) | биты (чт) |
| 0x0e0x7f | 114 байт NV RAM | пользовательские |
---
## 8.4 Джойстик
**Два Kempston-совместимых джойстика** [sprinter.cpp:11061175]:
- **JOY1** — через CIO_DTRB (DCP 0x15, совмещён с Beta state при чтении)
- **JOY2** — через PIO PB
**Формат данных (Kempston):**
| Бит | Кнопка |
|:---:|--------|
| 0 | Вправо |
| 1 | Влево |
| 2 | Вниз |
| 3 | Вверх |
| 4 | Fire |
| 5 | — |
| 6 | — |
| 7 | — |
**Противоречие: джойстик #1F vs #0F.**
> - [IvanMak.txt:1344]: «#1F,#0F — RD_KEMPS — порт джойстика.
> В Sprinter-1 порт #1F аппаратно переадресуется на порт #0F
> - [IvanMak.txt:861868]: команды `OUT (#1F),A` не срабатывают
> из-за перенаправления. Надо использовать `LD BC,#1F : OUT (C),A`.
>
> В Sprinter-1 включено аппаратное перенаправление `#1F → #0F`,
> из-за чего `IN A,(#1F)` не читает джойстик. MAME подтверждает:
> на Sp2000 (`m_cnf` bit 0) перенаправление отключается
> [sprinter.cpp:1106]. Ремап действует только на запись: при OUT
> порт `#1F` маппится на `#0F` через DCP [sprinter.cpp:1080].
JOY2 читается через PIO port A [sprinter.cpp:11361140].
---
## 8.5 Параллельный порт (принтер)
Centronics-совместимый через ППЛМ. BIOS-функции печати: 082h–08Ch
(см. `02-bios.md`).
---
## 8.6 Шина ISA
Два 8-битных ISA-слота на частоте X_SP/5 (~7 МГц). Доступ — через
DCP-декодер и страницы памяти.
**Управляющие порты ISA (DCP 0xD00xDF, 0xF80xFF):**
| Бит | Назначение |
|:---:|-----------|
| 2 | 0 = ISA memory access, 1 = ISA I/O access |
| 1 | Выбор слота (0 = slot 0, 1 = slot 1) |
Адресное расширение: `m_isa_addr_ext` (запись через DCP 0x1b) — биты
расширения адреса для ISA-циклов [sprinter.cpp:10321039].
**Memory-маппинг ISA:** При pg3 & 0xF9 == 0xD0 (бит 10 окна W3 = 1,
BANK_ISA_MASK) обращения к окну W3 (`#C000#FFFF`) направляются на ISA-шину
вместо RAM [sprinter.cpp:178181, 530532]. Длина — 16 КБ.
**I/O-доступ к ISA:** через тот же DCP-декодер. Устройства устанавливают
сигнал `m_io_input_wait` = true при совпадении адреса.
> **Примечание:** В MAME есть TODO: «ISA memory slots» — ISA memory-циклы
> пока не эмулируются [sprinter.cpp:576]. На реальном Sprinter поддержка
> зависит от прошивки ППЛМ.
Слот 0 обычно занят ZX-Bus-адаптером [sprinter.cpp:26].
---
## 8.7 Кросс-ссылки
- BIOS-функции: `02-bios.md`
- Клавиатура, мышь: `09-input.md`
- Звук: `10-sound.md`
- Порты: `11-ports.md`
- Акселератор: `06-accel.md`
- Прерывания: `07-irq.md`
+595
View File
@@ -0,0 +1,595 @@
# 9. Клавиатура и мышь
## 9.1 Общие сведения
Клавиатура Sprinter — AT-совместимая (101 key, PS/2). В Sprinter-1
дополнительно присутствует ZX-клавиатурная матрица через порт `#FE`
(скан-код через AT-контроллер преобразуется в ZX-матрицу). Sprinter-2+
использует AT-клавиатуру через внутренний SIO Z84C15 (канал A).
Мышь — Microsoft Serial Mouse (2 кнопки) через RS-232 на SIO-B (канал B
Z84C15).
### Порты SIO
| Порт | Назначение |
|------|-----------|
| `#18` | SIO-A data — клавиатура |
| `#19` | SIO-A control — статус/управление |
| `#1A` | SIO-B data — мышь |
| `#1B` | SIO-B control — мышь |
Бит 0 порта `#19` — признак прерывания от клавиатуры (используется в IM2
для различения источников с общим вектором `#FF`).
### Прерывания
Клавиатура и мышь работают в IM2, общий вектор `#FF` (совместно с
кадровым и CBL). Различение источника — по портам SIO и флаговым битам
(`07-irq.md §7.2`).
---
## 9.2 Клавиатура: BIOS и ESTEX
### FN_KBD_OUT (BIOS $EA)
Отправляет байт непосредственно на AT-клавиатуру [BIOS_v3.txt:0EAh].
```
Вход: C = $EA
A = байт команды/данных для AT-клавиатуры
Выход: нет
```
Используется для низкоуровневой настройки клавиатуры (LED-индикаторы,
повтор, задержка).
### ESTEX: функции клавиатуры (30h–37h)
Через `RST 10h` с C=30h37h [DiskSyscalls.txt:30h37h].
**WAITKEY (30h):** ждёт нажатия, возвращает код.
```
Вход: C = 30h
Выход: A = код символа
D = скан-код
E = ASCII-код (0 для функциональных клавиш)
```
**SCANKEY (31h):** опрос без ожидания.
```
Вход: C = 31h
Выход: A = код символа (0 если нажатий нет)
D = скан-код
E = ASCII-код
```
**ECHOKEY (32h):** ждёт + эхо-вывод.
```
Вход: C = 32h
Выход: A = код символа
D = скан-код
E = ASCII-код
```
**CTRLKEY (33h):** состояние модификаторов и режимов. libc: `kbd_mod_state()`.
```
Вход: C = 33h
Выход: C = mode (биты режимов — см. ниже)
B = shift (биты зажатых модификаторов)
```
| Бит C | Режим |
|-------|-------|
| 0 | РУС/ЛАТ (0=ЛАТ, 1=РУС) |
| 1 | NumLock |
| 2 | ScrollLock |
| 3 | CapsLock |
| 4 | Insert |
| Бит B | Модификатор |
|-------|------------|
| 0 | Left Shift |
| 1 | Right Shift |
| 2 | Ctrl (любой) |
| 3 | Alt (любой) |
| 4 | Left Ctrl |
| 5 | Left Alt |
| 6 | Right Ctrl |
| 7 | Right Alt |
**K_CLEAR (35h):** сбросить буфер клавиатуры.
```
Вход: C = 35h
```
**K_SETUP (36h):** настройка раскладки. [DiskSyscalls.txt:36h]
```
Вход: C = 36h
A = 0 — получить текущий номер раскладки
A = 1 — установить раскладку по номеру из B
A = 2 — получить имя текущей раскладки (адрес HL)
Выход: при A=0: A = номер раскладки
при A=2: HL = адрес строки с именем
```
| Номер | Раскладка |
|-------|----------|
| 0 | Английская (US) |
| 1 | Русская |
| 2 | Английская (UK) |
| 3 | Украинская |
Переключение раскладки по Ctrl+Space (если не переназначено).
**TESTKEY (37h):** тест на нажатие (без извлечения из буфера).
```
Вход: C = 37h
Выход: A = код символа (0 если нет)
D = скан-код
E = ASCII-код
```
---
## 9.3 Клавиатура: необработанный (raw) доступ
Штатный DSS (WAITKEY/SCANKEY/CTRLKEY) даёт событийный интерфейс —
нажатия, а не состояния клавиш. Для игр и real-time приложений требуется
чтение **зажатых клавиш в каждый момент**. Это реализуется через
перехват SIO-A напрямую в обработчике IM2, минуя DSS.
### 9.3.1 Принцип
1. Установить свой обработчик IM2 (общий вектор `#FF`).
2. В обработчике читать байты с SIO-A (порт `#18`), декодировать
PS/2 Scan Code Set 2, обновлять битовую карту held-состояний.
3. Основной код по кадровому прерыванию (или раз в кадр) читает карту
опросом — без ожидания.
4. DSS не получает байт с клавиатуры, пока обработчик их забирает.
Для восстановления нормального ввода — временно отключить перехват.
### 9.3.2 SIO-A и прерывания
SIO-A (порт данных `#18`, управление `#19`) работает в IM2.
**Инициализация SIO-A** (выполняется BIOS при загрузке, повторно не требуется):
| WR | Значение | Эффект |
|:--|:---------|--------|
| WR0 | `0x10` | Сброс RR1 (ошибка) |
| WR1 | `0x07` | Прерывания по Rx готовности |
| WR3 | `0xC1` | Rx 8 бит, без CRC |
| WR4 | `0x44` | ×1 клок, 1 стоп-бит, без паритета |
| WR5 | `0xEA` | Tx 8 бит, DTR+RTS=1 |
**Различение в IM2:** при входе в обработчик проверить бит 0 порта `#19`.
Если 0 — прерывание не от SIO-A (кадровое, CBL — пропустить).
### 9.3.3 PS/2 Scan Code Set 2 — протокол
SIO-A передаёт скан-коды PS/2 Set 2. Каждый байт с порта `#18`
один байт протокола. Размер сообщения:
| Последовательность | Значение |
|:------------------|:---------|
| `<code>` | Make — клавиша нажата (код < 0xF0) |
| `0xF0 <code>` | Break — клавиша отпущена |
| `0xE0 <code>` | Extended — расширенная клавиша (стрелки, RAlt, RCtrl) |
| `0xE0 0xF0 <code>` | Extended break |
Make-байт устанавливает бит клавиши в held-карте.
Break-байт сбрасывает.
**FSM декодирования (2 состояния):**
```
PEND_MAKE:
получить байт B
if B == 0xF0 → переход PEND_BREAK
if B == 0xE0 → переход PEND_EXT, Bзап = B
иначе → make(B)
PEND_BREAK:
получить байт B
переход PEND_MAKE
if (ext_flag) → break_ext(B); ext_flag = 0
иначе → break(B)
PEND_EXT:
получить байт B
if B == 0xF0 → ext_flag = 1, переход PEND_BREAK_EXT
иначе → make_ext(B); переход PEND_MAKE
PEND_BREAK_EXT:
получить байт B
ext_flag = 0
break_ext(B)
переход PEND_MAKE
```
### 9.3.4 Битовая карта held-состояний
Массив 128 байт (1024 бита для кодов 0..1023, расширенные — с битом
`0x0100`).
Простая реализация:
```asm
; HL = code (0..0x1FF)
; IX = base адреса карты
LD A, H
AND A, 0x07 ; номер бита 8..10
LD D, A
LD A, L
RRCA ; HL/8 (3 сдвига)
RRCA
RRCA
AND A, 0x1F
LD L, A
LD A, H
AND A, 0x07
ADD A, A ; +8*...
ADD A, A
ADD A, A
ADD A, L
LD L, A
LD H, 0x00
ADD IX, HL ; IX = &bitmap[code / 8]
LD A, D
INC A
LD B, A ; B = номер бита в байте + 1
XOR A
SCF ; carry=1 для make, 0 для break
RL A
DJNZ $-2 ; A = 1 << (code & 7)
; make: OR (IX), A ; break: CPL + AND (IX), A
```
### 9.3.5 Overrun recovery
SIO FIFO глубиной 3 байта. Если обработчик прерывания не успевает
забрать байты (длинная DI-секция, акселератор), FIFO переполняется —
RR1 bit5 = 1. С этого момента цепочка make+break нарушена, возможны
залипшие клавиши.
**Алгоритм восстановления:**
1. Обнаружить overrun (читать SIO RR1, бит 5).
2. Сбросить held-карту **всех клавиш, кроме**:
| Код PS/2 | Клавиша | Почему |
|:--------:|:--------|:-------|
| `0x12` | Left Shift | PS/2 автоповтор — только последняя |
| `0x59` | Right Shift | нажатая клавиша. Если сбросить |
| `0x14` | Left Ctrl | модификатор, он не восстановится до |
| `0x11` | Left Alt | физического отпускания. Игра «теряет» |
| `0xE014` | Right Ctrl | шифт/контрол/альт при каждом overrun. |
| `0xE011` | Right Alt | |
3. Взвести флаг «был overrun» — основной код может проигнорировать
held-состояние на этот кадр.
### 9.3.6 Координация с DSS
Пока обработчик забирает байты с SIO-A, FIFO пуст — DSS не видит
клавиатуру. Для вызова DSS-функций (консоль, диалоговые окна):
1. Переключить флаг «raw active = 0» в IM2-обработчике.
2. Обработчик начинает пропускать байты в DSS (JP `0038h` или
через штатный трамплин).
3. После завершения ввода — переключить флаг обратно.
**Внимание:** если обработчик просто перестаёт читать SIO-A, но не
возвращает управление DSS, порт SIO-A перестанет генерировать
прерывания (FIFO полон, Rx готов = 0). Для корректной передачи
управления нужно разрешить DSS-обработчику читать SIO-A.
### 9.3.7 Пример: минимальный raw-обработчик
```asm
; Флаги: raw_active = 1 — перехватывать, 0 — пропускать в DSS
; Карта: kbd_held — 128 байт, занулена при старте
; FSM: kbd_state — 0=PEND_MAKE, 1=PEND_BREAK, 2=PEND_EXT, 3=PEND_BREAK_EXT
; kbd_ext — флаг extended (0/1)
KBD_ISR:
; Проверить, что прерывание от SIO-A
IN A, (#19) ; SIO-A RR0
BIT 0, A ; бит 0 = прерывание от SIO-A?
RET Z ; нет — пропустить
IN A, (#18) ; прочитать байт скан-кода
BIT raw_active ; перехват активен?
JP Z, DSS_IRQ ; нет — пусть DSS обработает
; FSM: текущее состояние в kbd_state
LD HL, kbd_state
LD A, (HL)
AND A, 3
JP Z, .pend_make
DEC A
JP Z, .pend_break
DEC A
JP Z, .pend_ext
; .pend_break_ext
LD (HL), 0 ; → PEND_MAKE
LD A, B ; код клавиши
CALL break_ext
EI
RETI
.pend_make:
LD A, B ; байт с SIO-A (уже в B из IN)
CP 0xF0
JR Z, .set_break
CP 0xE0
JR Z, .set_ext
CALL make ; обычный make
LD (HL), 0 ; остаёмся PEND_MAKE
EI
RETI
.set_break:
LD (HL), 1 ; → PEND_BREAK
EI
RETI
.set_ext:
LD (HL), 2 ; → PEND_EXT
EI
RETI
.pend_break:
LD (HL), 0 ; → PEND_MAKE
CALL break
EI
RETI
.pend_ext:
LD A, B
CP 0xF0
JR Z, .set_ext_break
CALL make_ext
LD (HL), 0
EI
RETI
.set_ext_break:
LD (HL), 3 ; → PEND_BREAK_EXT
EI
RETI
```
Размер кода — ~60 байт.
---
## 9.4 Мышь
Драйвер мыши установлен в системном shell Sprinter и доступен через
SST-вызов `RST 30h`.
### 9.4.1 SST-функции мыши
Вызов: `RST 30h` с A = номер функции. Выход в зависимости от функции
(см. таблицу).
| № | Назначение | Вход | Выход |
|:-:|:-----------|:-----|:------|
| `$00` | Инициализация | A=$00 | — |
| `$01` | Показать курсор | A=$01 | — |
| `$02` | Спрятать курсор | A=$02 | — |
| `$03` | Читать состояние | A=$03 | DE=X, HL=Y, A=buttons (b0=left, b1=right) |
| `$04` | Переместить курсор | A=$04, DE=X, HL=Y | — |
| `$05` | Установить границы X | A=$05, DE=min, HL=max | — |
| `$06` | Установить границы Y | A=$06, DE=min, HL=max | — |
| `$07` | Вид курсора (текст) | A=$07, DE=sym_and:sym_xor, HL=attr_and:attr_xor | — |
| `$09` | Загрузить курсор (граф.) | A=$09, IX=pointer | — |
| `$0B` | Прочитать курсор | A=$0B | IX=pointer (копия) |
| `$0C` | Чувствительность X | A=$0C, D=value (1..255) | — |
| `$0D` | Чувствительность Y | A=$0D, D=value (1..255) | — |
| `$0E` | Прочитать чувств. X | A=$0E | D=value |
| `$0F` | Прочитать чувств. Y | A=$0F | D=value |
| `$11` | Уведомить о смене режима | A=$11, D=режим (0=текст, 1=граф-256, 2=граф-16) | — |
**Пример: прочитать состояние мыши в asm:**
```asm
LD A, $03 ; READ
RST 30h
; DE = X, HL = Y, A = buttons
LD (mouse_x), DE
LD (mouse_y), HL
AND A, 3
LD (mouse_btn), A
```
### 9.4.2 Чувствительность
Значение — **делитель**: драйвер считает N raw-шагов мыши, прежде чем
сдвинуть курсор на 1 пиксель. **Меньше = быстрее** (чувствительнее).
Рекомендуемое начальное значение: 2 по обеим осям.
```asm
LD A, $0C ; SET_SENS_H
LD D, 2
RST 30h
LD A, $0D ; SET_SENS_V
LD D, 2
RST 30h
```
### 9.4.3 Порядок инициализации
После загрузки драйвер мыши уже инициализирован shell. Если программа
начинает «с чистого листа» (без shell), инициализация обязательна:
```asm
LD A, $00 ; INIT — инициализировать драйвер
RST 30h
LD A, $05 ; BOUNDS_X: границы экрана
LD DE, 0
LD HL, 319 ; для 320×256
RST 30h
LD A, $06 ; BOUNDS_Y
LD DE, 0
LD HL, 255
RST 30h
LD A, $01 ; SHOW — показать курсор
RST 30h
```
### 9.4.4 Смена видеорежима
После переключения видеорежима (графический → текстовый и обратно)
обязательно вызвать `$11`:
```asm
LD A, $11
LD D, 1 ; 0=текст, 1=граф-256, 2=граф-16
RST 30h
```
Иначе драйвер применяет старые координаты, и курсор рисуется
неправильно.
### 9.4.5 Прямой доступ к SIO-B (raw mouse)
Мышь подключена через SIO-B (порты `#1A`/`#1B`), Microsoft Serial Mouse,
1200 бод 7E1. Если программа хочет читать мышь напрямую, минуя
SST-драйвер (например для нестандартного протокола или экономии на
SST), SIO-B нужно инициализировать самостоятельно:
**Инициализация SIO-B (1200 7E1):**
```asm
LD HL, .sio_init
LD B, 6
LD C, #1B ; SIO-B control
OTIR
JR .done
.sio_init:
DB #10 ; WR0: сброс RR1
DB #04 ; WR1: прерывания выкл
DB #44 ; WR4: ×1 клок, 1 стоп, без паритета
DB #C1 ; WR3: Rx 8 бит
DB #EA ; WR5: Tx 8 бит, DTR+RTS=1
DB #15 ; WR0: сброс ошибки + выбран RR1
.done:
```
**Чтение байта с мыши (опрос, без прерываний):**
```asm
IN A, (#1B) ; SIO-B RR0
BIT 0, A ; бит 0 = Rx готов?
JR Z, .no_data
IN A, (#1A) ; прочитать байт
; обработать ...
```
**Формат пакета Microsoft Serial Mouse:**
Длина: 3 байта.
| Байт | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|:----:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 0 | 1 | 0 | 0 | 0 | L | R | Y7 | Y6 |
| 1 | 0 | X6 | X5 | X4 | X3 | X2 | X1 | X0 |
| 2 | 0 | Y5 | Y4 | Y3 | Y2 | Y1 | Y0 | X7 |
- Координаты X/Y — **относительные** (приращения с последнего пакета), знаковые.
- `X7` — старший бит X (знак), `X6..X0` — младшие.
- `Y7..Y6` — старшие биты Y, `Y5..Y0` — младшие.
- `L` — левая кнопка (1 = нажата), `R` — правая.
**Декодирование:**
```asm
; B = байт 0, C = байт 1, D = байт 2
; X:
LD A, C ; байт 1: X6..X0
LD E, A
LD A, D ; байт 2: X7
RLCA
RLCA
AND A, 0x80
OR A, E ; A = X (7 бит + знак)
; знаковое расширение в HL
LD L, A
RLCA
SBC A, A
LD H, A ; HL = X (знаковое 16-бит)
; Y:
LD A, B ; байт 0: Y7..Y6
RLCA
RLCA ; A[1:0] = Y7..Y6
AND A, 0xC0
LD E, A
LD A, D ; байт 2: Y5..Y0
RLCA
RLCA ; A[7:2] = Y5..Y0
AND A, 0xFC
OR A, E ; A = Y (7 бит + знак)
; знаковое расширение в DE
LD E, A
RLCA
SBC A, A
LD D, A ; DE = Y (знаковое 16-бит)
; Кнопки:
LD A, B
AND A, 0x30 ; биты L (4) и R (5)
RRCA ; A = 0..3
RRCA
RRCA
RRCA
LD (buttons), A
```
---
## 9.5 Особенности игровой клавиатуры
Документ: `kbd-games.md`. Ниже — ключевые паттерны для реализации.
- **Held-state vs edge-detect:** DSS — событийный (сообщает только момент
нажатия). Raw-канал (§9.3) — state-based (зажата/не зажата прямо сейчас).
Играм нужен второй.
- **Sync once per frame:** раз в кадр (по кадровому прерыванию) проверять
held-карту. Между кадрами карта обновляется только в IM2-обработчике.
- **Edge-detect:** для одноразовых действий (прыжок, выстрел) — запоминать
предыдущее held-состояние и сравнивать:
```
pressed = held & ~prev_held
prev_held = held
```
- **Типоматик:** игнорировать повторные make, пока не придёт break.
Реализация: при make — установить бит только если он ещё не был
установлен; при break — сбросить.
- **Автоотмена (reject):** если за кадр нажато больше N клавиш (например
4) — игнорировать все (защита от баунса и мусора).
- **Recovery:** при overrun — очистить held-карту всех клавиш, кроме
модификаторов (LShift 0x12, RShift 0x59, LCtrl 0x14, LAlt 0x11,
RCtrl 0xE014, RAlt 0xE011).
---
## 9.6 Кросс-ссылки
- Прерывания (IM2, вектор #FF): `07-irq.md §7.2`
- DSS-функции клавиатуры: `03-dss.md §3.6`
- BIOS FN_KBD_OUT: `02-bios.md §2.7`
- Порт клавиатуры `#FE`: `11-ports.md §11.3`
- Игровая клавиатура: `kbd-games.md`
- Баги: `12-bugs.md`
- SIO и порты прерываний: `11-ports.md §11.2`
+377
View File
@@ -0,0 +1,377 @@
# 10. Звук
## 10.1 Общие сведения
> Документ описывает звуковую подсистему платы **Sp2000** и более новых моделей.
> Информация о Sprinter-97 не рассматривается.
Звуковая подсистема Sprinter Sp2000 включает три компонента:
| Компонент | Тип | Описание |
|-----------|-----|----------|
| GM80C760 (AY-3-8910 совм.) | Музыкальный сопроцессор | 3 голоса + шум + огибающая, эмуляция в ППЛМ |
| COVOX | 8-битный ЦАП | Прямой вывод сэмплов через порт `#FB`/`#4F` |
| CBL (COVOX-Blaster) | COVOX с буфером | 256 байт буфера, прерывания, разные режимы |
Весь звук выводится через **16-битный ЦАП TDA1543** (двухканальный, стерео).
Реально используется 10 бит для одновременного смешивания 8-bit COVOX и AY
[IvanMak.txt:632634].
**На Sp2000 все три компонента присутствуют в основной прошивке ППЛМ.**
CBL включается через порт `#4E` (бит 7 = 1). При выключенном CBL (бит 7 = 0)
работает обычный 8-bit COVOX [IvanMak.txt:662666].
## 10.2 GM80C760 (AY-3-8910)
### 10.2.1 Реализация
AY-функция реализована на микросхеме **GM80C760** (ППЛМ ALTERA EP1K30),
AY-3-8910/8912 совместимая. Это не отдельная дискретная микросхема
[IvanMak.txt:636637].
На EP1K30 (Sp2000) реализована **3-я версия** AY. Включает три голоса,
шум и генератор огибающей (envelope). В формирователе огибающей обнаружена
небольшая ошибка, детали не документированы [IvanMak.txt:224229],
[12-bugs.md §11.2].
### 10.2.2 Порты
| Порт | Назначение |
|------|-----------|
| `#BFFD` | AY-8910 — запись номера регистра (address) |
| `#FFFD` | AY-8910 — чтение/запись данных регистра (data) |
Внутренние номера в карте дешифратора ППЛМ: `90h` (`#BFFD`), `91h` (`#FFFD`)
[IvanMak.txt:12981299].
### 10.2.3 Программирование
Программирование — **стандартное для AY-3-8910/8912**
[IvanMak.txt:636637]:
```asm
; Установить регистр AY
LD BC, #BFFD
OUT (C), A ; A = номер регистра (0..15)
LD BC, #FFFD
OUT (C), A ; A = значение
; Прочитать регистр AY (3-я версия)
LD BC, #BFFD
OUT (C), A ; A = номер регистра
LD BC, #FFFD
IN A, (C) ; читать данные
```
**Регистры AY-3-8910** (стандартные):
| № | Назначение |
|:-:|-----------|
| 0 | Частота канала A (младший байт) |
| 1 | Частота канала A (старшие 4 бита) |
| 2 | Частота канала B (младший байт) |
| 3 | Частота канала B (старшие 4 бита) |
| 4 | Частота канала C (младший байт) |
| 5 | Частота канала C (старшие 4 бита) |
| 6 | Период шума |
| 7 | Микшер (включение каналов и шума) |
| 8 | Громкость A |
| 9 | Громкость B |
| 10 | Громкость C |
| 11 | Период огибающей (младший байт) |
| 12 | Период огибающей (старшие 8 бит) |
| 13 | Форма огибающей |
| 14 | Порт I/O A (не используется на Sprinter) |
| 15 | Порт I/O B (не используется на Sprinter) |
**Стерео:** AY имеет два выходных канала, но в стандартной конфигурации
Sprinter оба канала получают монофонический сигнал.
---
## 10.3 COVOX (8-бит)
### 10.3.1 Порты
| Порт | Назначение |
|------|-----------|
| `#FB` | COVOX data (он же порт включения КЭШ-ОЗУ по IN) |
| `#4F` | COVOX data (альтернативный адрес) |
[IvanMak.txt:642, 1347]
### 10.3.2 Программирование
Простейший вывод сэмплов — последовательная запись байтов в порт `#FB`
или `#4F`:
```asm
; Цикл вывода сэмплов (8 бит, моно)
LD HL, sample_data
LD BC, sample_length
loop:
LD A, (HL)
OUT (#FB), A
INC HL
DEC BC
LD A, B
OR C
JR NZ, loop
```
### 10.3.3 Конфликт с Fast RAM (КЭШ-ОЗУ)
Порт `#FB` используется **для двух целей**:
- `OUT (#FB), A` — запись в COVOX
- `IN A, (#FB)` — включение КЭШ-ОЗУ
Если КЭШ-ОЗУ включено, запись в `#FB` уходит в кэш, а не в COVOX.
Перед работой с COVOX кэш нужно отключать: `IN A, (#7B)`
[IvanMak.txt:850851], [12-bugs.md §11.4].
---
## 10.4 COVOX-Blaster (CBL)
### 10.4.1 Общее описание
**CBL (COVOX-Blaster)** — COVOX с буферным ОЗУ на **256 байт**. Позволяет
выводить звук поблочно, освобождая процессор для другой работы
[IvanMak.txt:647], [Parinov.txt:688].
### 10.4.2 Порт управления `#4E`
**Порт `#4E` — 16-битный! Только через `OUT (C),A`** (с `LD BC,004Eh`)
[Parinov.txt:692693].
> **Примечание:** IvanMak.txt упоминает его как 8-битный, но Parinov.txt явно
> указывает 16-битный доступ. Современный код использует `OUT (C),A`.
**Биты порта `#4E`** [Parinov.txt:695698]:
| Бит | Мнемоника | Назначение |
|-----|-----------|-----------|
| 7 | **CBL_ON** | 1 = CBL включён, 0 = обычный COVOX |
| 6 | **STEREO** | 1 = стерео, 0 = моно |
| 5 | **16BIT** | 1 = 16-битные сэмплы, 0 = 8-битные |
| 4 | **IRQ_EN** | 1 = включить прерывания от CBL |
| 30 | **FREQ** | Частота дискретизации (см. таблицу) |
### 10.4.3 Частоты дискретизации
[Parinov.txt:700718]:
| Код | Частота | Примечание |
|:---:|---------|-----------|
| 0 | 16 kHz | старые режимы, **НЕ ИСПОЛЬЗОВАТЬ** |
| 1 | 22 kHz | старые режимы, **НЕ ИСПОЛЬЗОВАТЬ** |
| 2–7 | — | зарезервировано |
| **8** | **7.8125 kHz** | mono/stereo, 8/16-bit |
| **9** | **10.9375 kHz** | mono/stereo, 8/16-bit |
| **A** | **15.625 kHz** | mono/stereo, 8/16-bit |
| **B** | **21.875 kHz** | mono/stereo, 8/16-bit |
| **C** | **31.25 kHz** | mono/stereo, 8/16-bit |
| **D** | **43.75 kHz** | mono/stereo, 8/16-bit |
| **E** | **54.6875 kHz** | mono/stereo, 8/16-bit |
| **F** | **109.375 kHz** | mono/stereo, 8/16-bit |
> **Важно:** коды 0 и 1 — старые режимы из первых прошивок, несовместимы с
> новыми режимами (8–F). Использовать только коды 8–F.
### 10.4.4 Режимы и формат данных
[Parinov.txt:767783]:
| Режим | Нулевой уровень | Размер блока | Формат данных |
|-------|:---------------:|:------------:|--------------|
| mono 8-bit | `80h` | 128 байт | `DB 80h, 81h, 7Fh, ...` |
| mono 16-bit | `0000h` | 256 байт | `DW 0, 1000, -1000, ...` |
| stereo 8-bit | `80h,80h` | 128 байт | `DB 80h,80h, 81h,80h, ...` (L, R чередуются) |
| stereo 16-bit | `0000h,0000h` | 256 байт | `DW 0,0, 1000,0, ...` (L, R чередуются) |
Формат данных выбран так, чтобы WAV-файлы можно было пересылать в CBL
простой `OTIR` [Parinov.txt:791].
### 10.4.5 Буфер CBL и механизм банок
CBL имеет буферное ОЗУ на **256 байт**, организованное как кольцевой буфер
[Parinov.txt:758760]:
- Счётчик CBL считает **«назад»** — из-за особенности команды `OUTI`, где `B`
уменьшается и попадает на A15..A8.
- Для 8-битных режимов буфер условно разбит на **две банки по 128 байт**.
- Для 16-битного режима блок = **256 байт** (весь буфер).
**Механизм работы:**
1. Процессор записывает блок данных (128 байт для 8-бит, 256 байт для 16-бит)
в буфер CBL.
2. Аппаратный счётчик CBL автоматически считывает данные из буфера и
отправляет в ЦАП на заданной частоте.
3. Бит 7 порта `#FE` указывает, какая банка сейчас выводится в ЦАП.
4. Когда банка переключается — CBL запрашивает следующую порцию данных
(через прерывание или polling).
5. Запрос данных возникает для **каждых 128 байт (256 в 16-битном режиме)**.
### 10.4.6 Порт статуса `#FE`
**Бит 7 порта `#FE`** — запрос данных для CBL [Parinov.txt:720724]:
| Бит | Назначение |
|-----|-----------|
| 7 | **CBL data request** — 1 = CBL запрашивает новую порцию данных |
| 5 | **Frame sync** — кадровый импульс (4 мс длиной, 20 мс период) |
> **Примечание:** В MAME при выключенном CBL бит 7 порта #FE всегда = 1.
> Это следует учитывать при разработке обработчика прерываний
> [`im2_isr_design.md:2631`].
**Противоречие** [08-io.md §8.3]:
- IvanMak.txt описывает бит 7 как старший бит счётчика CBL для polling
(определение, какая половина буфера выводится).
- Parinov.txt описывает его как запрос прерывания.
- Оба описывают одну физическую линию, но в разных режимах (polling vs interrupt).
### 10.4.7 Прерывания и IM2
- При включённых прерываниях CBL (бит 4 порта `#4E` = 1) необходим **режим IM 2**.
- Если остаться на IM1, DOS-процедура прерываний будет вызываться слишком часто,
что вызовет тормоза на высоких частотах [Parinov.txt:762764].
- **Вектор прерывания CBL — `#FF`** (как и у клавиатуры, и у кадрового).
Отличать — по биту 7 порта `#FE` [`sprinterIntLib.asm:2426`].
**Обработчик прерываний на IM2:**
```asm
; Проверка источника
IN A, (#FE)
RLCA ; бит 7 → CF
JR NC, not_cbl ; не CBL — на другой обработчик
; CBL запрашивает данные
LD BC, 804Fh ; B = 128 (счётчик), C = 4Fh (порт CBL data)
LD HL, next_block
OTIR ; вывести 128 байт в буфер CBL
...
```
### 10.4.8 Запись данных в CBL
**Способ 1: OTIR/OUTI через порт `#4F`**
```asm
LD BC, 804Fh ; B = 128 (счётчик), C = #4F (CBL data)
LD HL, data_block
OTIR ; вывести 128 байт
```
**Способ 2: через страницу ОЗУ `#FD` с акселератором**
Страница `#FD`, отображённая в окно `#C000`, аппаратно связана с буфером CBL.
Для 8-битного режима — записать 128 байт; для 16-битного — 256 байт
[Parinov.txt:727731].
```asm
; Запись блока данных в CBL через страницу #FD с акселератором
IN A, (#E2) ; сохранить текущую страницу W3
LD (.save_page), A
LD A, #FD
OUT (#E2), A ; W3 = страница CBL
LD DE, #C000 ; приёмник (CBL buffer)
LD B, 0 ; B = 0 → 256 байт (или 128 для 8-bit)
LD D,D ; ACC_ON: включить акселератор
LD L,L ; ACC_CopyBlock_Horizontal
LD A, (HL) ; загрузить байт в буфер акселератора
LD (DE), A ; записать в CBL
LD B,B ; ACC_OFF: выключить акселератор
LD A, (.save_page)
OUT (#E2), A ; восстановить страницу
```
### 10.4.9 Полный пример
Из `docs/samples/Пример для CBL.asm` [sprinterIntLib.asm]:
```asm
; Инициализация IM2
LD A, #BE
LD I, A ; таблица векторов по #BE00
IM 2
; Вектор прерывания CBL = #BEFF
; (Interrupt_Vector OR 0FFh)
; Включение CBL
LD BC, #4E
LD A, %10011000b ; bit7=1 CBL on, bit4=1 IRQ en, bits3-0=1000 = 7.8125 kHz
OUT (C), A
; В ISR:
; IN A, (#FE)
; RLCA
; JR NC, .NoSound
; ... копировать 128 байт через страницу #FD с акселератором
; После проигрывания:
; Установить NumberPage=2, подождать HALT
; Выключение CBL:
LD BC, #4E
LD A, 0
OUT (C), A
```
### 10.4.10 Известные проблемы
1. **CBL vs Fast RAM:** порт `#FB` используется и для COVOX/CBL, и для
включения КЭШ-ОЗУ (см. §10.3.3).
2. **Щелчок перед первым звуком:** известная проблема при старте CBL
[`TODO.md:263`].
3. **Помехи при движении мыши:** часть тиков прерываний, обслуживающих
CBL-насос, уходит на обслуживание мыши [`TODO.md:270277`].
4. **Порт #4E — 8-бит vs 16-бит:** IvanMak подразумевает 8-битный,
Parinov требует 16-битный `OUT (C),A` (см. §10.4.2).
5. **MAME: бит 7 порта #FE = 1 при выключенном CBL:** может вызвать
ложные срабатывания обработчика (см. §10.4.6).
---
## 10.5 Бипер
Стандартный Spectrum-бипер — бит 3 (в некоторых конфигурациях бит 5)
порта `#FE`. Выведен через ту же схему TDA1543, что COVOX и AY
[IvanMak.txt:639640].
```asm
; Включить бипер
LD A, %00001000 ; бит 3 = 1
OUT (#FE), A
; Выключить бипер
LD A, %00000000 ; бит 3 = 0
OUT (#FE), A
```
---
## 10.6 SSC (Sprinter Sound Card)
Разрабатывалась, но **не реализована** ни в одной прошивке Sp2000
из-за большого объёма ПЛМ [IvanMak.txt:763772].
Параметры проекта:
- 2 канала
- 16 восьмибитных голосов
- 256-байтные Wave-таблицы для каждого голоса
- 8-битный регулятор амплитуды
---
## 10.7 Кросс-ссылки
- Порты звука: `11-ports.md`
- Баги AY и CBL: `12-bugs.md`
- Fast RAM и конфликт с COVOX: `04-memory.md §4.6`
- 08-io.md (остальной ввод-вывод): `08-io.md`
+103
View File
@@ -0,0 +1,103 @@
# 11. Карта портов
## 11.1 Системные порты Z84C15 (внутренние)
Эти адреса изменить невозможно — находятся вне ППЛМ [IvanMak.txt:869870].
| Порт | Назначение | Доступ |
|------|-----------|--------|
| `#10` | SIO A data / CTC channel 0 | R/W |
| `#11` | SIO A control / CTC channel 1 | R/W |
| `#12` | SIO B data / CTC channel 2 | R/W |
| `#13` | SIO B control / CTC channel 3 | R/W |
| `#14` | PIO A data | R/W |
| `#15` | PIO A control | R/W |
| `#16` | PIO B data | R/W |
| `#17` | PIO B control | R/W |
| `#18` | SIO data channel A (клавиатура) | R/W |
| `#19` | SIO control channel A (статус клавиатуры) | R/W |
| `#1A` | SIO data channel B (мышь) | R/W |
| `#1B` | SIO control channel B (мышь) | R/W |
| `#1E` | PIO B бит 0 — IRQ от ISA | R |
| `#1F` | PIO B + джойстик (Kempston) | R/W |
| `#EE` | CTC channel 2 (альтернативный) | R/W |
| `#EF` | CTC channel 3 (альтернативный) | R/W |
| `#F0` | CTC channel 0 (альтернативный) | R/W |
| `#F1` | CTC channel 1 (альтернативный) | R/W |
| `#F4` | CTC — альтернативный доступ | |
## 11.2 Управление памятью
| Порт | Назначение | Источник |
|------|-----------|----------|
| `#7FFD` | Основной: W3 page (02), RAM/ROM select, lock | IvanMak.txt:1343 |
| `#1FFD` | Расширенный: W0 page (02), turbo, extended RAM, VRAM segment | IvanMak.txt:1345 |
| `#DFFD` | W1 page select (Pentagon-512) | IvanMak.txt:1346 |
| `#EFF7` | W2 page select (Pentagon-512) | IvanMak.txt:1346 |
| `#82` | W0 page (PAGE0) — всегда доступен | IvanMak.txt:1348 |
| `#A2` | W1 page (PAGE1) — всегда доступен | IvanMak.txt:1348 |
| `#C2` | W2 page (PAGE2) — всегда доступен | IvanMak.txt:1348 |
| `#E2` | W3 page (PAGE3) — всегда доступен | IvanMak.txt:1348 |
## 11.3 Видео
| Порт | Назначение | Источник |
|------|-----------|----------|
| `#89` | RGADR (PORT_Y) — номер строки/блока видео-ОЗУ | IvanMak.txt:1349 |
| `#C9` | RGMOD — страница режима экрана (бит 0: 0/1) | IvanMak.txt:1350 |
## 11.4 Управление конфигурацией/прошивкой
| Порт | Назначение | Источник |
|------|-----------|----------|
| `#FB` | Включение КЭШ-ОЗУ (IN), COVOX data | IvanMak.txt:1351 |
| `#7B` | Выключение КЭШ-ОЗУ (IN) | Parinov.txt:598 |
## 11.5 Дисковая подсистема
| Порт | Назначение | Источник |
|------|-----------|----------|
| `#0F` | ВГ93 Command/Status (после ремапинга #1F#0F) | IvanMak.txt:1270 |
| `#1F` | ВГ93 Command/Status (оригинал, ремапится) | IvanMak.txt:1344 |
| `#3F` | ВГ93 Track register | IvanMak.txt:1271 |
| `#5F` | ВГ93 Sector register | IvanMak.txt:1272 |
| `#7F` | ВГ93 Data register | IvanMak.txt:1273 |
| `#FF` | ВГ93 Drive Control (write); IRQ/джойстик (read) | IvanMak.txt:1274 |
| `#BD` | Переключение режима FDD (720K/1.44M) | IvanMak.txt:1347 |
| `xx50` | IDE Data | Parinov.txt:958 |
| `xx51` | IDE Error/Features | Parinov.txt:958 |
| `xx52` | IDE Sector Count | Parinov.txt:958 |
| `xx53` | IDE Sector Number | Parinov.txt:958 |
| `xx54` | IDE Cylinder Low | Parinov.txt:958 |
| `xx55` | IDE Cylinder High | Parinov.txt:958 |
| `4052h` (R) / `4152h` (W) | IDE Device/Head | Parinov.txt:959 |
| `4053h` (R) / `4153h` (W) | IDE Status/Command | Parinov.txt:959 |
| `FFBD` | CMOS data read | BIOS_v3.txt:308 |
| `BFBD` | CMOS data write | BIOS_v3.txt:308 |
| `DFBD` | CMOS address write | BIOS_v3.txt:308 |
## 11.6 Звук
| Порт | Назначение | Источник |
|------|-----------|----------|
| `#FB` | COVOX data (also КЭШ-ОЗУ enable) | IvanMak.txt:642 |
| `#4F` | COVOX/CBL data | IvanMak.txt:642 |
| `#4E` | CBL control (8/16-bit — см. `10-sound.md §9.4.2`) | IvanMak.txt:662, Parinov.txt:692 |
## 11.7 Джойстик и системные
| Порт | Назначение | Источник |
|------|-----------|----------|
| `#FE` | ZX-клавиатурная матрица, бипер, CBL статус | IvanMak.txt:1344 |
| `#1F` | Джойстик (Kempston) / PIO B | IvanMak.txt:1344 |
## 11.8 Кросс-ссылки
- BIOS-функции: `02-bios.md`
- DSS-функции: `03-dss.md`
- Управление памятью: `04-memory.md`
- Звук (AY, COVOX, CBL): `10-sound.md`
- Видео/палитра: `05-graphics.md`
- Прерывания: `07-irq.md`
- Детали I/O: `08-io.md`
- Баги: `12-bugs.md`
+54
View File
@@ -0,0 +1,54 @@
# 12. Баги BIOS, DSS и железа Sp2000
## 12.1 DRV_VERIFY (54h) не существует
Функция `54h DRV_VERIFY` описана во всей документации BIOS, но в реальности отсутствует — всегда возвращает CF=1.
> [bugs.txt:5859]: «Нет биосной функции "54h" (DRV_VERIFY) верификации секторов, описанной в документации. Поэтому она всегда возвращает установленный флаг "Carry".»
Источники, содержащие описание несуществующей функции:
- [BIOS_v3.txt:258261]
- [IvanMak.txt:21982210]
## 12.2 AY-3-8910 — ошибка в формирователе огибающей
Третья версия AY в ППЛМ Sp2000 содержит ошибку в схеме формирователя огибающей (envelope generator). Детали не документированы.
> [IvanMak.txt:224229]: «Третья версия схемы AY ... включает в себя ... генератор огибающей. ... В схеме формирователя огибающей обнаружена небольшая ошибка. ... В следующей версии AY предполагается данный недостаток исключить.»
## 12.3 Акселератор — конфликт с прерываниями
При работе акселератора система команд Z80 сильно меняется, что может приводить к сбоям в обработчиках прерываний.
> [IvanMak.txt:844846]: «Отключение прерываний во время работы акселератора необходимо, так как в этот момент сильно меняется система команд процессора и программа на прерывании не сможет работать нормально.»
Новая прошивка акселератора позволяет работать с прерываниями — при приходе INT акселератор отключается и включается обратно по RETI [IvanMak.txt:846848]. Без тестирования на реальном железе неизвестно, какая версия прошивки установлена.
## 12.4 Быстрое ОЗУ — конфликт с COVOX
Порт `#FB` используется как для включения КЭШ-ОЗУ (IN), так и для вывода данных в COVOX (OUT). Если КЭШ-ОЗУ включено, запись в COVOX может давать неожиданные результаты.
> [IvanMak.txt:850851, 642]
Перед вызовом RST или выходом в DOS КЭШ-ОЗУ обязательно отключать (`IN A,(#7B)`).
## 12.5 STA_VID — потеря старшего байта (0A8h?)
> [bugs.txt:33]: «В БИОСе в функции STA_VID (возможно 0A8h) теряется старший байт параметра. В v2.12 функцию рекомендуется вызывать с учетом этого.»
Относится к BIOS v2.12. Не проверялось, исправлено ли в v3.00.
## 12.6 Несовместимость с реальным железом — только MAME
Многие перечисленные баги и квирки зафиксированы в документации, но не подтверждены на реальном Sprinter Sp2000. Все тесты тулчейна выполняются в MAME. Актуальность багов для реального железа неизвестна.
> [TODO.md]: «Проверка на реальном железе — в планах»
## 12.7 Кросс-ссылки
- BIOS v3.00: `02-bios.md`
- DSS/ESTEX: `03-dss.md`
- Акселератор: `06-accel.md`
- Fast RAM: `04-memory.md §4.6`
- Проектный TODO: `TODO.md`
- Проектный список багов: `converted/bugs.txt`
+169
View File
@@ -0,0 +1,169 @@
# 13. Информация из Telegram-чатов (Sp2022 и развитие Sprinter)
## 13.1 Источники
Данные извлечены из 319 HTML-файлов чатов Telegram, выгруженных из чата по Sprinter (n/w). Обработано 317 602 сообщения, отфильтровано технических — 72 307 (15 МБ текста).
Основные авторы технических сообщений: RomanRom2, KromosS, solegstar, nzeemin, holub.
## 13.2 BIOS 3.06 / DSS 1.71.57
KromosS (23 января 2026): «I am running the DSS 1.71.57 / Bios 3.06».
Зафиксированные версии BIOS:
- 3.05 — предыдущая стабильная
- 3.06 — текущая, 23.01.2026
- Прошивка ППЛМ и ROM собираются через Intel Quartus
BIOS 3.06 собрана из ROM-файла, загружаемого через загрузчик. Процесс сборки:
1. Конфигурация ППЛМ через Quartus
2. Сборка ROM-образа (IS28F020 — 256 КБ flash)
3. Программирование через специальный загрузчик
## 13.3 Эволюция аппаратных платформ
RomanRom2 (11 ноября 2025) подробно описал историю развития Sprinter в 10 пунктах:
| Этап | Платформа | Описание | Период |
|------|-----------|----------|--------|
| 1 | Sp2000 | Оригинальная плата, 8 DIP VRAM | 20002001 |
| 2 | Sp2000s | Баг в разводке ALTERA: ISA зеркалированы | ~2001 |
| 3 | Sp2000s Light | Баг фикс с MGTF, ISA распаяны не полностью | ~2002 |
| 4 | Sp2021s | Новая ревизия от solegstar | ~2021 |
| 5 | Sp2022s/d | Собственный дизайн (Zeal Hardware / RomanRom2) | 2022 |
| 6+ | Sp2024d/du | Планируется, с USB-контроллером | ~2024 |
### Детали:
**Sp2000 (оригинал)**
- Плата 2000–2001 года
- ПЗУ 256 КБ, VRAM 256 КБ (8 DIP-микросхем)
- ALTERA ACEX 1K EP1K30
**Sp2000s**
- ALTERA-баг: в оригинальной плате зеркально разведены ISA-слоты, на Sp2000s исправлено
- Новый баг разводки: ISA адреса «перевёрнуты» — при устанавке платы в слот слоты просто не видятся. В Sp2000s Light фикс с MGTF (соединительная плата), один ISA-слот не распаян
**Sp2022 (Zeal Hardware)**
- Новая разработка на современной элементной базе
- Варианты: Sp2022s (Sp2000 layout, SMD), Sp2022d (две платы, digitized)
- SMD-компоненты, технология 2022 года
- Расширенная память, улучшенное питание
- Sp2022du — вариант с USB-контроллером (планируется для Sp2024)
**Sp2024**
- Sp2024d — следующее поколение
- Sp2024du — с интегрированным USB для клавиатуры/мыши
- Планируется USB-контроллер
### Sp2022 — модификации
| Модификация | Описание |
|-------------|----------|
| sp2022s | SMD, layout аналогичен Sp2000 |
| sp2022d | Две платы (digitized), расширенная память |
| sp2022d-black | Чёрная текстолит |
| sp2022release | Готовая плата для пользователей |
| sp2022m | Мобильная версия (будущее) |
| sp2022i | ITX-формфактор |
| sp2022atx | ATX-формфактор |
| sp2022t | Новая ревизия |
| sp2022du | С USB-контроллером |
## 13.4 Изменения компонентов
### ПЗУ и загрузка
- ROM: IS28F020 (256 КБ Flash, 5V)
- Загрузка через Quartus → ROM-образ → загрузчик
- ROM-файлы обсуждаются в чате
### VRAM
- Оригинал Sp2000: CY7C109D-10VXI (5V, 128K×8)
- Sp2022: CY7C1019DV33-10VXI (3.3V, 128K×8)
- Переход на 3.3V логику
### Video-DAC
- Sp2022: обсуждаются TDA8772 (трипл 8-бит video-DAC) или ADV7120/ADV7125
- Некоторые платы используют ADV7125 (трипл 10-бит) ???
### CMOS
- DS12887A (оригинал)
- На Sp2022 — замена на современный аналог
### КЭШ-ОЗУ
- W24512 (64K×8) — для Sp2022
### SDRAM
- Оригинал: 72-pin SIMM
- Sp2022: SDRAM (DDR) на плате
## 13.5 Аппаратные баги
### Sp2000s — ALTERA routing bug
При переразводке Sp2000 под Sp2000s допущена ошибка в разводке ALTERA (ACEХ 1K). ISA-адреса оказались «зеркальными» — при установке платы в слот половинка линий данных перевёрнута, из-за чего слоты не видятся CPU. Исправлено на Sp2000s Light с помощью MGTF (мезонинная плата-переходник).
### Sp2000s Light — ISA unsoldered
На Sp2000s Light один ISA-слот не распаян, второй — через MGTF.
### IDE active short
В некоторых ревизиях — короткое замыкание на линиях IDE при активном доступе. Требует подтягивающих резисторов.
### FDISK CHS-LBA
Типовые проблемы с разметкой дисков — ошибки пересчёта CHS в LBA в утилитах FDISK для Sprinter.
## 13.6 MAME-эмуляция
- holub разрабатывает форк MAME для Sprinter
- Timing issues: Z80 Z84C15 эмулируется не полностью (внутренние PIO/SIO/CTC)
- Работа над корректной эмуляцией Sp2022
## 13.7 Статистика по платформам
| Платформа | Упоминаний | Период |
|-----------|-----------|--------|
| sp2000 | 243 | 20002001 |
| sp2022 | 122 | 20222024 |
| sp2022d | 66 | 20222024 |
| sp2000s | 44 | 20012002 |
| sp2022s | 43 | 20222024 |
| sp2024d | 6 | 2024+ |
| sp2024du | 4 | 2024+ (план) |
| sp2022i | 3 | 20222024 |
| sp2022m | 1 | будущее |
## 13.8 Компонентная статистика
| Компонент | Упоминаний |
|-----------|-----------|
| Z80 | 1317 |
| USB | 800 |
| BIOS v3.xx | 813 |
| MAME | 496 |
| DDR | 172 |
| ACEX | 147 |
| ALTERA | 115 |
| CY7C | 82 |
| Z84C15 | 87 |
| TDA1543 | 55 |
| SDRAM | 48 |
| IS28F020 | 30+ |
| Quartus | упоминается |
## 13.9 Противоречия с docs/new/
### BIOS v3.06 / DSS 1.71.57
Основные источники (BIOS_v3.txt, IvanMak.txt) документируют BIOS 3.00, не 3.06. Версия 3.06 обнаружена только в Telegram. DSS 1.71.57 значительно новее, чем 1.60 в документации.
### Sp2022 — новое железо
Вся документация в docs/converted/, part2/ и reference/ описывает Sp2000 (20002001). Sp2022 — полностью новая платформа с современной элементной базой (SMD, 3.3V VRAM, USB, SDRAM). Документация отсутствует.
### Состав BIOS
Неизвестно, какие функции из IvanMak.txt (HDD, RAM-disk, конфигурационные, портовые) реализованы в BIOS 3.06, а какие нет. Также неизвестно, исправлены ли баги (DRV_VERIFY, AY envelope).
## 13.10 Кросс-ссылки
- Аппаратная архитектура Sp2000: `01-architecture.md`
- BIOS v3.00: `02-bios.md`
- Проектный TODO: `TODO.md`
- Сырые данные: `docs/tg/_messages.jsonl`, `docs/tg/_technical.txt`
- Фильтр: `docs/tg/_filter.py`
+112
View File
@@ -0,0 +1,112 @@
# INDEX — сводный указатель документации Sp2000
## Документы
| № | Файл | Содержание |
|---|------|-----------|
| 01 | `01-architecture.md` | Общая архитектура: процессор, ППЛМ, конфигурации, обзор памяти/видео/акселератора/звука/ISA/клавиатуры/мыши/дисков/прерываний |
| 02 | `02-bios.md` | BIOS v3.00: вызовы (RST 8/18h, CALL 3D13h), полная таблица функций, видео/палитра/диск/принтер |
| 03 | `03-dss.md` | DSS/ESTEX v1.60: файловые функции, диски, память, консоль, коды ошибок |
| 04 | `04-memory.md` | Память: 4 окна, страницы, порты (#7FFD/#1FFD/#DFFD/#EFF7), EXE-формат, Fast RAM |
| 05 | `05-graphics.md` | Графика: VRAM, адресация (спектрумовская/графическая), видеорежимы, палитра, shadow RAM |
| 06 | `06-accel.md` | Акселератор: команды, примеры, скорость, прерывания |
| 07 | `07-irq.md` | Прерывания: IM1/IM2, INT, CTC, SIO, CBL, цепочка обработчиков |
| 08 | `08-io.md` | Ввод-вывод: FDD, HDD, CMOS, джойстик, принтер, ISA, карта портов |
| 09 | `09-input.md` | Клавиатура (BIOS/DSS/raw) и мышь (RST 30h): API, протоколы, прерывания |
| 10 | `10-sound.md` | Звук: AY-3-8910, COVOX, COVOX-Blaster (CBL), бипер, SSC |
| 11 | `11-ports.md` | Сводная карта портов: Z84C15, память, видео, диски, звук, конфигурация |
| 12 | `12-bugs.md` | Баги: DRV_VERIFY, AY envelope, акселератор+IRQ, Fast RAM+COVOX |
| 13 | `13-telegram.md` | Telegram-чаты: BIOS 3.06, эволюция Sp2000→Sp2022, Sp2022 варианты, Sp2024, аппаратные баги, MAME |
## Противоречия — сводка
### Критические (меняют ABI)
| Тема | Файл | Источники |
|------|------|-----------|
| SETWIN2: 39h vs 3Ah | `03-dss.md` | DiskSyscalls.txt vs ProgrammerManual.txt |
| EXCMDLN subfunc 5: смещения | `03-dss.md` | Все источники дают неверные DE+3/DE+4 |
### Средние
| Тема | Файл | Источники |
|------|------|-----------|
| WIN_RESTORE_WIN: C=0B2h (опечатка) | `02-bios.md` | BIOS_v3.txt vs IvanMak.txt |
| WIN_MOVE_WIN: C=0B2h (опечатка) | `02-bios.md` | IvanMak.txt vs BIOS_v3.txt |
| WIN_GET_SYM: A=0 обязательно? | `02-bios.md` | Parinov.txt vs BIOS_v3.txt |
| PIC_SET_PAL: битфилд vs номер | `02-bios.md` | BIOS_v3.txt vs IvanMak.txt |
| LP_PRINT_SYM: 082h=(131) неверно | `02-bios.md` | Ошибка десятичной аннотации |
| FN_VERSION: значения BC | `02-bios.md` | BIOS_v3.txt vs IvanMak.txt |
### Низкие
| Тема | Файл | Источники |
|------|------|-----------|
| CBL порт 8/16-бит | `10-sound.md` | IvanMak vs Parinov |
| CBL bit7 #FE: статус vs IRQ | `10-sound.md` | IvanMak vs Parinov |
| Адресация ВГ93: #7F data reg | `08-io.md` | IvanMak vs Parinov; MAME подтверждает IvanMak |
| HDD: R/W разные адреса | `08-io.md` | IvanMak vs Parinov; MAME использует единую карту DCP |
| Джойстик #1F vs #0F | `08-io.md` | IvanMak: ремап #1F#0F; MAME на Sp2000 отключает |
## Кросс-ссылки между документами
```
01-architecture.md ← 02-bios.md (BIOS call conventions)
← 04-memory.md (memory overview)
← 05-graphics.md (video overview)
← 06-accel.md (accel overview)
← 07-irq.md (interrupt overview)
← 08-io.md (disk, joystick, printer, ISA overview)
← 09-input.md (keyboard, mouse overview)
← 10-sound.md (sound overview)
← 11-ports.md (ISA ports)
02-bios.md ← 05-graphics.md (PIC_SET_PAL, SET_MODE)
← 11-ports.md
← 12-bugs.md (DRV_VERIFY)
03-dss.md ← 04-memory.md (SETWIN/SETWIN1/SETWIN2)
← 11-ports.md
← 12-bugs.md
04-memory.md ← 06-accel.md (Fast RAM conflict with accel)
← 11-ports.md
← 03-dss.md (window functions)
05-graphics.md ← 02-bios.md (PIC_SET_PAL, SETVMOD)
← 06-accel.md (accelerator for fast pixel ops)
← 10-sound.md (CBL for video)
← 11-ports.md (RGADR, RGMOD)
07-irq.md ← 10-sound.md (CBL interrupt sources)
← 11-ports.md
← 08-io.md (disk I/O interrupt sources)
← 09-input.md (keyboard, mouse interrupt sources)
08-io.md ← 02-bios.md (printer functions)
← 09-input.md (keyboard, mouse moved here)
← 10-sound.md
← 11-ports.md
← 07-irq.md (mouse IRQ)
11-ports.md → все документы
```
## Проектные документы (не входят в docs/new/)
| Файл | Связь |
|------|-------|
| `memory-management.md` | C-specific memory models (legacy, перекрывается `04-memory.md`) |
| `fast_ram.md` | Детальный анализ Fast RAM — уточняет `04-memory.md §4.6` |
| `file-buffering-design.md` | FILE* дизайн — уточняет `03-dss.md` |
| `im2_isr_design.md` | IM2/ISR дизайн — уточняет `07-irq.md` |
| `sprite-api-design.md` | Спрайтовое расширение BGI |
| `kbd-games.md` | Клавиатура в играх — уточняет `09-input.md §9.5` |
| `libc-reference.md` | API libc |
| `libc-roadmap.md` | План работ по libc |
| `libc-split-asm-cases.md` | Правила сплита asm |
| `libc-headers.md` | Стратегия заголовков |
| `accel-fill-budget.md` | Бюджет тактов акселератора |
| `TODO.md` | Roadmap проекта |
| `bugs/` | Баг-репорт SDCC z80 |
Binary file not shown.
Binary file not shown.
+17 -10
View File
@@ -1,5 +1,5 @@
# Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
accfill 3772 accfill 3786
argv 3431 argv 3431
assrtest 3847 assrtest 3847
atlas 9193 atlas 9193
@@ -9,15 +9,15 @@ banked 1056
bankedbg 1067 bankedbg 1067
banklocl 4683 banklocl 4683
banktest 3767 banktest 3767
bgi_img 7057 bgi_img 7200
bgitest 3737 bgitest 3751
bios_text 4461 bios_text 4461
blitperf 5860 blitperf 5860
blitw 4939 blitw 4939
cat 913 cat 913
cblstream 6217 cblstream 6431
cbltest 6366 cbltest 6580
cblwav 6434 cblwav 6648
conio 4605 conio 4605
conio2 3929 conio2 3929
dec_test 860 dec_test 860
@@ -27,12 +27,12 @@ fdmax 6023
filetest 10547 filetest 10547
fpsdiv 4870 fpsdiv 4870
gets 509 gets 509
gfx_dbuf 5032 gfx_dbuf 5112
gfx_demo 4103 gfx_demo 4117
gfxbanks 6044 gfxbanks 6044
hello 4167 hello 4167
hello2 4244 hello2 4244
irqtest 6527 irqtest 6647
kbdraw 4911 kbdraw 4911
ls 4835 ls 4835
malloc 4461 malloc 4461
@@ -45,7 +45,8 @@ palfile 5406
ptime 5744 ptime 5744
rpgprof 12082 rpgprof 12082
rpgwalk 10309 rpgwalk 10309
rt_test 4892 rt_test 5154
scroll 2974
seek 4188 seek 4188
simple 955 simple 955
solidt 11531 solidt 11531
@@ -60,4 +61,10 @@ strtest 1340
text_palette 5024 text_palette 5024
timedir 5454 timedir 5454
w0page 8831 w0page 8831
w3bankgfx 5240
w3bgfx 5246
w3big 3673
w3huge 3700
w3probe 3556
w3tiny 3471
winrest 4459 winrest 4459
1 # Эталон размеров _CODE (байт); обновление: python3 toolchain/size_check.py --update
2 accfill
3 argv
4 assrtest
5 atlas
9 bankedbg
10 banklocl
11 banktest
12 bgi_img
13 bgitest
14 bios_text
15 blitperf
16 blitw
17 cat
18 cblstream
19 cbltest
20 cblwav
21 conio
22 conio2
23 dec_test
27 filetest
28 fpsdiv
29 gets
30 gfx_dbuf
31 gfx_demo
32 gfxbanks
33 hello
34 hello2
35 irqtest
36 kbdraw
37 ls
38 malloc
45 ptime
46 rpgprof
47 rpgwalk
48 rt_test
49 scroll
50 seek
51 simple
52 solidt
61 text_palette
62 timedir
63 w0page
64 w3bankgfx
65 w3bgfx
66 w3big
67 w3huge
68 w3probe
69 w3tiny
70 winrest
+38
View File
@@ -0,0 +1,38 @@
/*
* gfx_blit_part_noclip блит ПОД-ПРЯМОУГОЛЬНИКА картинки getimage-формата
* БЕЗ клипа по экрану, в ТЕКУЩЕМ банке (gfx_set_bank). Пара к
* gfx_blit_noclip ровно так же, как gfx_blit_part к gfx_blit.
*
* Зачем: тайловому движку часто нужно положить поверх спрайта не весь
* тайл, а только ту его часть, что перекрывает спрайт. Через
* gfx_blit_part это идёт общим ядром _gfx_blit_full (16-битная
* арифметика, клип, нарезка полос) ~13 К тактов накладных на вызов
* независимо от размера; здесь тот же под-прямоугольник рисуется линейным
* спрайтовым ядром (~4.6 К на 32×3). Замер PoP roomtest 2026-07-29:
* fore-слой поверх персонажа рисовал 9 целых тайлов за 432 К тактов
* (стена = блок 64×63); с обрезкой по прямоугольнику спрайта тех же
* пикселей остаётся втрое меньше.
*
* Требования (проверяет ВЫЗЫВАЮЩИЙ, кода проверок здесь нет):
* - под-прямоугольник (sx,sy,w,h) лежит внутри картинки;
* - прямоугольник назначения целиком на экране;
* - w,h <= 255, y <= 255.
* Не выполняется зовите gfx_blit_part.
*
* W3-скобка ставится ЗДЕСЬ (код libbgi живёт в W1): из приложения,
* собранного с --w3, её вызывать нельзя после _bgi_begin окно W3 занято
* видеобанком и код вызывающего исчезает из адресного пространства.
*/
#include "../_bgi.h"
void gfx_blit_part_noclip(int x, int y, const void *img,
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h)
{
const uint8_t *p = (const uint8_t *)img;
uint16_t stride = (uint16_t)(p[0] | (p[1] << 8));
const uint8_t *src = p + 4 + (uint16_t)sy * stride + sx;
_bgi_begin();
_gfx_blit_sprite_noclip(x, y, src, stride, w, h);
_bgi_end();
}
+9 -3
View File
@@ -46,10 +46,16 @@ void gfx_scroll_h(const gfx_rect_t *area, int16_t dx, gfx_rect_t *dirty) {
* DI/EI-банды по 16 строк leaf делает сам (h: 0=256). Между бандами по * DI/EI-банды по 16 строк leaf делает сам (h: 0=256). Между бандами по
* ширине CPU-адрес двигаем на 256 (Port_Y выбирает строку). */ * ширине CPU-адрес двигаем на 256 (Port_Y выбирает строку). */
while (w) { while (w) {
uint8_t cw = (w > 256) ? 256 : (uint8_t)w; /* 0 => 256 */ // Закомментированный блок должен работать правильно но не работает
// почему-то при w > 256 второй проход мусорит
// TODO: разобраться
// uint8_t cw = (w > 256) ? 256 : (uint8_t)w; /* 0 => 256 */
// _bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)area->y);
// src += 256; dst += 256;
// w -= (w > 256) ? 256 : w;
uint8_t cw = (w > 255) ? 255 : (uint8_t)w; /* 0 => 256 */
_bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)area->y); _bgi_scroll_rows_raw(src, dst, cw, h, (uint8_t)area->y);
src += 256; dst += 256; src += cw; dst += cw; w -= cw;
w -= (w > 256) ? 256 : w;
} }
_bgi_end(); _bgi_end();
+8
View File
@@ -105,6 +105,14 @@ void gfx_heal_noclip(int x, int y, uint8_t w, uint8_t h);
void gfx_blit_part(int x, int y, const void *img, void gfx_blit_part(int x, int y, const void *img,
int sx, int sy, int w, int h); int sx, int sy, int w, int h);
/* Тот же под-прямоугольник БЕЗ клипа по экрану — линейным спрайтовым
* ядром (пара к gfx_blit_noclip). Вызывающий гарантирует: под-
* прямоугольник внутри img, назначение целиком на экране, w,h,y <= 255.
* Для тайловых движков, кладущих поверх спрайта только перекрывающую его
* часть тайла (PoP: fore-слой). */
void gfx_blit_part_noclip(int x, int y, const void *img,
uint8_t sx, uint8_t sy, uint8_t w, uint8_t h);
/* Блит спрайта column-major (пиксели по колонкам) — вертикальным accel- /* Блит спрайта column-major (пиксели по колонкам) — вертикальным accel-
* проходом. flip!=0 = горизонтальное зеркало (направление персонажа) без * проходом. flip!=0 = горизонтальное зеркало (направление персонажа) без
* CPU-реверса/второй копии; зеркало ВНУТРИ футпринта [x,x+w) (для «hot- * CPU-реверса/второй копии; зеркало ВНУТРИ футпринта [x,x+w) (для «hot-
+4 -4
View File
@@ -30,10 +30,10 @@
#define _IRQ_VEC_BUF_SIZE (256 + 257) #define _IRQ_VEC_BUF_SIZE (256 + 257)
/* Буфер под W2-копию кадрового трамплина (small/huge — _irq_tramp в /* Буфер под W2-копию кадрового трамплина (small/huge — _irq_tramp в
* W1). С запасом над реальным размером трамплина (244 Б после * W1). С запасом над реальным размером трамплина (267 Б после
* FIFO-drain + overrun-recovery в raw-клавиатурной ветке, 2026-07-18 * упаковки held-карты клавиатуры в биты, 2026-07-30 проверять
* проверять __irq_tramp_end при правках, запас держать 60 Б). */ * __irq_tramp_end при правках, запас держать >= 60 Б). */
#define _IRQ_TRAMP_BUF_SIZE 320 #define _IRQ_TRAMP_BUF_SIZE 336
extern uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE]; extern uint8_t _irq_vec_buf[_IRQ_VEC_BUF_SIZE];
View File
+31 -5
View File
@@ -10,7 +10,8 @@
* (порт данных 0x18 читается один раз, деструктивно либо мы, * (порт данных 0x18 читается один раз, деструктивно либо мы,
* либо DSS, не оба), декодируется make/break (PS/2 Scan Code * либо DSS, не оба), декодируется make/break (PS/2 Scan Code
* Set 2: 0xF0 префикс отпускания, 0xE0 префикс расширенной * Set 2: 0xF0 префикс отпускания, 0xE0 префикс расширенной
* клавиши) в битовую карту `_kbdraw_down[]`, и к DSS НЕ идём * клавиши) в битовую карту `_kbdraw_down[]` (бит на код, 64 Б),
* и к DSS НЕ идём
* (private RETI тот же паттерн, что и у CBL ниже). Пока raw * (private RETI тот же паттерн, что и у CBL ниже). Пока raw
* открыт, DSS клавиатуру не видит вообще (см. <kbd_raw.h>). * открыт, DSS клавиатуру не видит вообще (см. <kbd_raw.h>).
* - CBL (бит 7 порта 0xFE) только при зарегистрированном хуке * - CBL (бит 7 порта 0xFE) только при зарегистрированном хуке
@@ -61,6 +62,7 @@ void _irq_tramp(void) __naked
;; break-кода = клавиша «залипает» навсегда (найдено 2026-07-16). ;; break-кода = клавиша «залипает» навсегда (найдено 2026-07-16).
push hl push hl
push de push de
push bc ; маска бита строится в BC (см. ниже)
tr_kbd_drain: tr_kbd_drain:
in a, (#_IRQ_SIO_DATA) ; читаем тем самым дренируем байт in a, (#_IRQ_SIO_DATA) ; читаем тем самым дренируем байт
cp #0xF0 cp #0xF0
@@ -68,7 +70,27 @@ void _irq_tramp(void) __naked
cp #0xE0 cp #0xE0
jr Z, tr_kbd_setext jr Z, tr_kbd_setext
;; --- обычный байт кода: индекс в _kbdraw_down[код(+256 EXT)] --- ;; --- обычный байт кода: БИТ code&7 в байте _kbdraw_down[code>>3]
;; (карта упакована по битам: 64 Б вместо 512, см. _kbdraw.h).
;; Маску строим сдвигом, а НЕ таблицей: таблица потребовала бы
;; `ld hl,#метка` внутри трамплина, а он копируется в W2 побайтно
;; и обязан быть без абсолютных само-ссылок (см. шапку). ---
ld e, a ; E = код
and #7
ld b, a
inc b ; B = (код&7)+1 для djnz
ld a, #1
jr tr_kbd_mskdec
tr_kbd_mskloop:
add a, a
tr_kbd_mskdec:
djnz tr_kbd_mskloop ; A = 1 << (код&7)
ld c, a ; C = маска бита
ld a, e
rrca
rrca
rrca
and #0x1F ; A = код>>3 (код <= 255)
ld hl, #__kbdraw_down ld hl, #__kbdraw_down
ld d, #0 ld d, #0
ld e, a ld e, a
@@ -76,16 +98,19 @@ void _irq_tramp(void) __naked
ld a, (__kbdraw_pending) ld a, (__kbdraw_pending)
bit 1, a ; _KBDRAW_PEND_EXT? bit 1, a ; _KBDRAW_PEND_EXT?
jr Z, tr_kbd_noext jr Z, tr_kbd_noext
ld de, #256 ld de, #32 ; +256 кодов = +32 байта карты
add hl, de add hl, de
tr_kbd_noext: tr_kbd_noext:
ld a, (__kbdraw_pending) ld a, (__kbdraw_pending)
bit 0, a ; _KBDRAW_PEND_BREAK? bit 0, a ; _KBDRAW_PEND_BREAK?
jr Z, tr_kbd_mkdown jr Z, tr_kbd_mkdown
xor a, a ; break снять (0) ld a, c
cpl ; break сбросить бит
and a, (hl)
jr tr_kbd_wr jr tr_kbd_wr
tr_kbd_mkdown: tr_kbd_mkdown:
ld a, #1 ; make взвести (1) ld a, c ; make взвести бит
or a, (hl)
tr_kbd_wr: tr_kbd_wr:
ld (hl), a ld (hl), a
xor a, a xor a, a
@@ -120,6 +145,7 @@ void _irq_tramp(void) __naked
ld a, #1 ld a, #1
ld (__kbdraw_overrun), a ld (__kbdraw_overrun), a
tr_kbd_noovr: tr_kbd_noovr:
pop bc
pop de pop de
pop hl pop hl
pop af pop af
+7 -2
View File
@@ -23,7 +23,12 @@ extern uint8_t _kbdraw_pending; /* FSM декодера make/break, см. в
extern uint8_t _kbdraw_overrun; /* 1 = ISR поймал Rx-overrun SIO (потерян extern uint8_t _kbdraw_overrun; /* 1 = ISR поймал Rx-overrun SIO (потерян
* байт возможно потерян break); снимает * байт возможно потерян break); снимает
* kbd_raw_sync (чистит _kbdraw_down) */ * kbd_raw_sync (чистит _kbdraw_down) */
extern uint8_t _kbdraw_down[512]; /* 0/1 по коду; +256 для расширенных /* Held-состояние: БИТ на скан-код, код>>3 = байт, код&7 = номер бита;
* (префикс 0xE0) см. kbd_raw_down */ * коды 0..255 обычные, 256..511 расширенные (префикс 0xE0), т.е.
* смещение +32 байта. Упаковка вместо байта на код: 64 Б вместо 512
* заметная доля _DATA на 32-килобайтной раскладке (замер PoP roomtest
* 2026-07-30). Ведёт трамплин прерывания (libc/irq/_irq_tramp.c),
* читает kbd_raw_down(). */
extern uint8_t _kbdraw_down[64];
#endif #endif
+1 -1
View File
@@ -9,4 +9,4 @@ uint8_t _kbdraw_active;
uint8_t _kbdraw_pending; uint8_t _kbdraw_pending;
uint8_t _kbdraw_overrun; /* ISR взвёл при Rx-overrun SIO (потерян uint8_t _kbdraw_overrun; /* ISR взвёл при Rx-overrun SIO (потерян
* break) kbd_raw_sync снимет залипшее */ * break) kbd_raw_sync снимет залипшее */
uint8_t _kbdraw_down[512]; uint8_t _kbdraw_down[64]; /* БИТ на скан-код (см. _kbdraw.h) */
+1 -1
View File
@@ -10,5 +10,5 @@
uint8_t kbd_raw_down(uint16_t code) uint8_t kbd_raw_down(uint16_t code)
{ {
if (code >= 512) return 0; if (code >= 512) return 0;
return _kbdraw_down[code]; return (uint8_t)((_kbdraw_down[code >> 3] >> (code & 7)) & 1);
} }
+10 -2
View File
@@ -36,7 +36,15 @@ void kbd_raw_sync(void)
if (!_kbdraw_overrun) return; if (!_kbdraw_overrun) return;
_kbdraw_overrun = 0; _kbdraw_overrun = 0;
for (i = 0; i < 6; i++) save[i] = _kbdraw_down[_kbd_mod_codes[i]]; /* Карта упакована по битам (см. _kbdraw.h): сохраняем/возвращаем
* именно БИТ модификатора, а не байт. */
for (i = 0; i < 6; i++) {
uint16_t c = _kbd_mod_codes[i];
save[i] = (uint8_t)((_kbdraw_down[c >> 3] >> (c & 7)) & 1);
}
memset(_kbdraw_down, 0, sizeof(_kbdraw_down)); memset(_kbdraw_down, 0, sizeof(_kbdraw_down));
for (i = 0; i < 6; i++) _kbdraw_down[_kbd_mod_codes[i]] = save[i]; for (i = 0; i < 6; i++) {
uint16_t c = _kbd_mod_codes[i];
if (save[i]) _kbdraw_down[c >> 3] |= (uint8_t)(1 << (c & 7));
}
} }
+24
View File
@@ -0,0 +1,24 @@
# w3bankgfx — пробник модели «huge + резидент W3 + банк W3 + графика».
#
# Проверяем ровно то, на чём стоит план раскладки PoP (docs/layout_plan_v2.md):
# R2 из __banked-кода резидентная страница W3 недостижима (НЕ зовём);
# R3 резидент -> банк работает через трамплин;
# R4 примитивы libbgi можно звать ИЗ БАНКА: _bgi_begin запоминает текущую
# страницу W3 (порт 0xE2) и _bgi_end её возвращает — значит банк
# переживает рисование и продолжает исполняться.
#
# Раскладка huge: CODE 0x4100 (W1), DATA 0x8000 (W2), банк 0x1C000 (W3),
# резидент --w3 делит W3 с банком (crt0 запоминает резидентную страницу).
PROJ_ROOT := $(abspath $(CURDIR)/../..)
EXAMPLE := w3bgfx
MEMORY := huge
EXTRA_FLAGS := --gfx 256 --w3 res.c --bank 1=bank1.c
include $(PROJ_ROOT)/app.mk
# Ожидаемый результат (проверено 2026-07-29, MAME dev):
# пять полос сверху вниз: синяя (резидент), бирюзовая (банк рисует сам),
# жёлтая (W1/W2-функция, вызванная из банка), ЗЕЛЁНАЯ (все проверки ок),
# синяя (резидент жив после возврата). Красная вместо зелёной = модель
# не подтвердилась. Диагностика в переменных bank_page_*/home_page_seen/
# res_page_seen: банк 0xF0, резидент 0xF3, страница W3 внутри банка не
# менялась вокруг рисования.
+48
View File
@@ -0,0 +1,48 @@
/*
* bank1.c __banked модуль (страница W3 подменяется трамплином на время
* вызова). Задача: проверить, что ИЗ БАНКА можно рисовать примитивами
* libbgi и звать функции W1/W2, и что после этого банк ЖИВ (его страница
* вернулась в W3).
*
* Резидент (res_draw) отсюда НЕ зовём сознательно: пока исполняется банк,
* резидентной страницы в адресном пространстве нет (правило R2).
*/
#include <graphics.h>
#include <stdint.h>
extern uint8_t home_helper(uint8_t x); /* функция в W1/W2 (рисует сама) */
/* ВАЖНО: писучие статики __banked-модуля линкуются В СТРАНИЦУ БАНКА
* (адрес 0x1C000+), поэтому снаружи их не прочитать из W1/W2 по 0xC000
* видна РЕЗИДЕНТНАЯ страница. Состояние банка держим в главном модуле. */
extern uint8_t bank_page_before, bank_page_after, bank_page_after_home;
extern uint8_t bank_alive;
/* Текущая страница W3 (порт 0xE2). ОТДЕЛЬНОЙ __naked-функцией: инлайновый
* `in a,(0xE2)` посреди тела затирает A, куда SDCC уже положил параметр
* в первой версии пробника из-за этого цвет заливки становился номером
* страницы (0xF3 = чёрный), и «резидент не рисовал». */
static uint8_t w3_page(void) __naked
{
__asm__("in a, (#0xE2)\n\tret");
}
uint8_t bank_run(uint8_t arg) __banked
{
uint8_t r;
bank_page_before = w3_page();
/* 1) прямое рисование примитивом libbgi (он живёт в W1/W2 и сам
* открывает/закрывает W3-скобку) */
setfillstyle(SOLID_FILL, CYAN);
bar(70, 40, 120, 52);
bank_page_after = w3_page();
/* 2) вызов функции W1/W2, которая тоже рисует */
r = home_helper(arg);
bank_page_after_home = w3_page();
/* 3) банк ещё исполняется — значит его страница на месте */
bank_alive = 1;
return (uint8_t)(r + 1);
}
+27
View File
@@ -0,0 +1,27 @@
/*
* res.c РЕЗИДЕНТНЫЙ модуль окна W3 (--w3): лежит на 0xC000 и вызывается
* напрямую из W1/W2. Рисует через готовые примитивы libbgi это законно:
* скобку _bgi_begin/_bgi_end они открывают внутри себя (сам W3-код открывать
* её НЕ имеет права, иначе исполняемая страница подменяется видеобанком).
*/
#include <graphics.h>
#include <stdint.h>
uint8_t res_page_seen; /* какая страница была в W3 во время вызова */
/* Текущая страница W3 (порт 0xE2). ОТДЕЛЬНОЙ __naked-функцией: инлайновый
* `in a,(0xE2)` посреди тела затирает A, куда SDCC уже положил параметр
* в первой версии пробника из-за этого цвет заливки становился номером
* страницы (0xF3 = чёрный), и «резидент не рисовал». */
static uint8_t w3_page(void) __naked
{
__asm__("in a, (#0xE2)\n\tret");
}
void res_draw(int y, int color)
{
res_page_seen = w3_page();
setfillstyle(SOLID_FILL, color);
bar(10, y, 60, y + 12);
}
+85
View File
@@ -0,0 +1,85 @@
/*
* w3bankgfx пробник раскладки «huge + резидент W3 + банк W3 + графика».
* См. Makefile: проверяются правила R2/R3/R4 из
* applications/PoP/docs/layout_plan_v2.md §2.
*
* Итог рисуется полосами, чтобы читалось и глазом, и скриптом по VRAM:
* y=20 синяя рисовал РЕЗИДЕНТ W3 (до банка)
* y=40 бирюз. рисовал БАНК напрямую примитивом libbgi
* y=60 жёлтая рисовала функция W1/W2, вызванная ИЗ БАНКА
* y=80 зелёная все проверки прошли (иначе КРАСНАЯ)
* y=100 синяя резидент W3 жив ПОСЛЕ возврата из банка
*/
#include <graphics.h>
#include <stdio.h>
#include <stdint.h>
#include <sprinter.h>
const uint8_t n_banks = 1; /* читает crt0_banked */
extern uint8_t res_page_seen;
uint8_t bank_page_before, bank_page_after, bank_page_after_home, bank_alive;
extern uint8_t bank_pages[];
void res_draw(int y, int color); /* резидент W3 (прямой вызов) */
uint8_t bank_run(uint8_t arg) __banked; /* банк (через трамплин) */
uint8_t home_page_seen;
/* Текущая страница W3 (порт 0xE2). ОТДЕЛЬНОЙ __naked-функцией: инлайновый
* `in a,(0xE2)` посреди тела затирает A, куда SDCC уже положил параметр
* в первой версии пробника из-за этого цвет заливки становился номером
* страницы (0xF3 = чёрный), и «резидент не рисовал». */
static uint8_t w3_page(void) __naked
{
__asm__("in a, (#0xE2)\n\tret");
}
/* Функция W1/W2, которую зовёт БАНК. Рисует — то есть внутри неё libbgi
* подменит W3 на видеобанк и вернёт обратно страницу БАНКА, а не резидента. */
uint8_t home_helper(uint8_t x)
{
home_page_seen = w3_page();
setfillstyle(SOLID_FILL, YELLOW);
bar(130, 60, 180, 72);
return (uint8_t)(x * 2);
}
int main(void)
{
uint8_t ret, ok;
uint8_t res_page, bank_page;
initgraph();
setfillstyle(SOLID_FILL, BLACK);
bar(0, 0, 319, 255);
res_draw(20, BLUE); /* R3-часть: W1/W2 -> резидент W3 */
res_page = res_page_seen;
ret = bank_run(21); /* R3: резидент/HOME -> банк */
bank_page = bank_page_before;
/* Критерии: банк дожил до конца, вернул r+1 = 21*2+1 = 43, страница W3
* ВНУТРИ банка не менялась вокруг рисования (её вернули), и она НЕ равна
* резидентной. */
ok = (uint8_t)(bank_alive == 1 && ret == 43 &&
bank_page_after == bank_page &&
bank_page_after_home == bank_page &&
home_page_seen == bank_page &&
bank_page != res_page);
setfillstyle(SOLID_FILL, ok ? GREEN : RED); /* зелёная / красная */
bar(10, 80, 60, 92);
res_draw(100, BLUE); /* резидент жив после возврата? */
printf("res_page=%02X bank_page=%02X after=%02X afterhome=%02X home=%02X ret=%u alive=%u ok=%u\n",
res_page, bank_page, bank_page_after, bank_page_after_home,
home_page_seen, ret, bank_alive, ok);
printf("bank_pages[1]=%02X\n", bank_pages[1]);
while (1) { } /* держим картинку для скрипта */
return 0;
}
+13 -2
View File
@@ -11,6 +11,9 @@
# #
# Использование: # Использование:
# toolchain/make_hdd.sh mame/v306/IMG/test_hdd.chd prog.exe data1 data2 ... # toolchain/make_hdd.sh mame/v306/IMG/test_hdd.chd prog.exe data1 data2 ...
# toolchain/make_hdd.sh out.chd prog.exe KID:kid0.atl GUARD:g0.atl ...
# — аргумент вида КАТАЛОГ:файл кладёт файл в подкаталог образа
# (каталог создаётся); без префикса файл идёт в корень.
set -e set -e
if [ $# -lt 2 ]; then if [ $# -lt 2 ]; then
@@ -39,12 +42,20 @@ export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1
mpartition -I -c -t $CYLS -h $HEADS -s $SECS z: mpartition -I -c -t $CYLS -h $HEADS -s $SECS z:
mformat z: mformat z:
# Аргумент может быть либо файлом (кладём в корень), либо парой
# КАТАЛОГ:файл — тогда каталог создаётся на образе и файл кладётся туда.
# Имена на Sprinter/DSS — 8.3, каталоги тоже (KID, GUARD, BG, LEVELS...).
for f in "$@"; do for f in "$@"; do
mcopy -o "$f" z: case "$f" in
*:*) dir="${f%%:*}"; src="${f#*:}"
mmd "z:/$dir" 2>/dev/null || true
mcopy -o "$src" "z:/$dir/" ;;
*) mcopy -o "$f" z: ;;
esac
done done
echo "--- содержимое образа ---" echo "--- содержимое образа ---"
mdir z: mdir -/ z:
# RAW -> CHD (без сжатия = быстро; та же геометрия). # RAW -> CHD (без сжатия = быстро; та же геометрия).
chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \ chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \