6673279cef
Скелет (L3-SKEL, ассеты + механика): - pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3 curr_guard_color = 0, оригинал палитру не подменяет); - pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом стража и был невидим; - load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень — только в кадрах 150..189. Пока ветка была одна (charid_2_guard), скелет получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался; - check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в комнате 3 при падении (seg002:252), autocontrol_skeleton; - leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня. Цвета стражей (BUG-GUARD-COLOR-1, закрыт): - все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257). Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP меняется вместе со стражем. Грабля: gfx_pal_load отдаёт указатель в BIOS, а тот читает только #4000-#BFFF — таблицу из банка копируем в стек. Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт): - pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19. Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать обратно. +48 байт W2. Окклюзия соперника: - pop_fore_over_char получил проход other_overlay_tile (порядок midtable, seg008:1B06) и расширение перебора объединённым прямоугольником «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх кладки и верхней грани пола; - клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP; - ROOMNAV после смерти Кида делает честный pop_start_level: телепорт «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с вернувшейся кучей костей. Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком (101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) — render_room анимированные тайлы пропускает, и в атласе не было ни одного. Число EMM-страниц не изменилось. Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок (резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен гард «код наехал на данные» (DATA_LOC). Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3: level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT). В roomtest.c временно оставлен автостоп на падении соперника (отладка падений скелета) — помечен ВРЕМЕННО. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
176 lines
8.8 KiB
C
176 lines
8.8 KiB
C
/*
|
||
* kbd_raw_poll — вычерпать приёмный FIFO клавиатуры ОПРОСОМ, не дожидаясь
|
||
* прерывания. Дополняет (не заменяет) декодер в трамплине: тот же разбор
|
||
* PS/2 Set 2, та же битовая карта _kbdraw_down[], тот же FSM префиксов
|
||
* _kbdraw_pending — просто вызванный из главного цикла приложения.
|
||
*
|
||
* ЗАЧЕМ (разбор 2026-08-01, applications/PoP/roomtest/TASKS_CLOSED.md, KBD-1).
|
||
* Приёмный FIFO SIO — 3 байта, и рассчитывать на то, что каждый принятый
|
||
* байт разбудит нас прерыванием, НЕЛЬЗЯ:
|
||
* - импульс запроса прерывания клавиатуры живёт лишь 32 такта CPU
|
||
* (dev-MAME: sprinter.cpp on_kbd_data → irq_off_timer), а ядра
|
||
* акселератора держат DI на весь блит — сотни микросекунд. Импульс,
|
||
* попавший в такое окно, теряется НАСОВСЕМ: байт остаётся в FIFO до
|
||
* следующего прерывания (следующий байт либо кадровое, 50 Гц);
|
||
* - трафик от клавиатуры пачками: тап стрелки — 5 байт (E0 75 + E0 F0 75),
|
||
* одновременное отпускание нескольких клавиш — больше. Три ячейки FIFO
|
||
* такую пачку не держат: потерянный make = «нажатие не сработало»,
|
||
* потерянный break = залипшая клавиша (её потом лечит kbd_raw_sync,
|
||
* ценой сброса всех немодификаторных клавиш).
|
||
* Опрос из главного цикла снимает зависимость от пойманного импульса: чем
|
||
* чаще позвали, тем меньше шанс, что в FIFO успеет накопиться четвёртый
|
||
* байт. Звать между фазами кадра (после тяжёлых блитов), НЕ раз в кадр.
|
||
*
|
||
* ABI: __sdcccall(1), без аргументов, результат uint8 в A — 0 если FIFO
|
||
* был пуст (быстрый выход, ~40 тактов), 1 если что-то вычерпано. IX не
|
||
* трогаем. Тело идёт под DI: чтение порта 0x18 деструктивно, и гонка с
|
||
* трамплином за read-modify-write ячейки _kbdraw_down[] стоила бы
|
||
* потерянного бита. DI/EI — БЕЗУСЛОВНЫЕ (как IRQ_DISABLE/IRQ_ENABLE в
|
||
* kbd_raw_open): функция рассчитана на вызов из главного цикла, где
|
||
* прерывания разрешены; звать её из ISR нельзя.
|
||
*/
|
||
|
||
#include <kbd_raw.h>
|
||
#include "_kbdraw.h"
|
||
#include "../irq/_irq.h"
|
||
|
||
uint8_t kbd_raw_poll(void) __naked
|
||
{
|
||
__asm
|
||
ld a, (__kbdraw_active)
|
||
or a, a
|
||
jr Z, kp_off ; raw-канал закрыт — не наше дело
|
||
;; DI ДО пробы «есть ли байт»: иначе между пробой и DI успело бы
|
||
;; влезть прерывание, трамплин вычерпал бы FIFO, а мы всё равно
|
||
;; пошли бы читать порт 0x18 — и получили бы из пустого приёмника
|
||
;; мусорный «скан-код» (взведённый бит клавиши, которую не нажимали).
|
||
di
|
||
in a, (#_IRQ_SIO_A) ; SIO-A RR0
|
||
rrca ; бит 0 — байт принят?
|
||
jr C, kp_work
|
||
ei
|
||
kp_off:
|
||
xor a, a
|
||
ret
|
||
|
||
kp_work:
|
||
push hl
|
||
push de
|
||
push bc
|
||
kp_drain:
|
||
in a, (#_IRQ_SIO_DATA) ; чтение деструктивно = дренаж байта
|
||
cp #0xF0
|
||
jp Z, kp_setbrk ; jp, а не jr: тело цикла длиннее 127 Б (fake shift)
|
||
cp #0xE0
|
||
jp Z, kp_setext ; (в отличие от трамплина, здесь jp разрешён)
|
||
|
||
;; --- «fake shift» — тот же разбор, что в трамплине (см. _kbdraw.h):
|
||
;; расширенные 12/59 не клавиши, а служебная обёртка, которую
|
||
;; НЕКОТОРЫЕ клавиатуры шлют вокруг расширенного кода, пока реально
|
||
;; зажат Shift. Только подтверждение «зажат», обратного вывода нет. ---
|
||
ld e, a ; E = код
|
||
ld a, (__kbdraw_pending)
|
||
bit 1, a ; EXT?
|
||
ld a, e ; A = код обратно (ld флаги не трогает)
|
||
jr Z, kp_code ; обычный код — прежний путь
|
||
cp #0x12
|
||
jr Z, kp_fake
|
||
cp #0x59
|
||
jr Z, kp_fake
|
||
;; расширенная КЛАВИША — обычный путь. О состоянии Shift по ней НЕ
|
||
;; судим (почему — см. kp_fake). A всё ещё = код.
|
||
jr kp_code
|
||
|
||
kp_fake:
|
||
;; Бит взводит ОБЩИЙ писатель ниже: обнуляем префиксы — и он кладёт
|
||
;; код в PLAIN-половину карты (без +32) и именно ВЗВЕДЁТ бит, а не
|
||
;; снимет, даже у break-формы обёртки.
|
||
xor a, a
|
||
ld (__kbdraw_pending), a
|
||
ld a, e
|
||
;; fall through в kp_code
|
||
|
||
;; --- обычный байт кода: БИТ code&7 в _kbdraw_down[code>>3].
|
||
;; Маска сдвигом, а не таблицей — ровно как в трамплине, чтобы
|
||
;; расхождения декодеров были невозможны by construction. ---
|
||
kp_code:
|
||
ld e, a ; E = код
|
||
and #7
|
||
ld b, a
|
||
inc b ; B = (код&7)+1 для djnz
|
||
ld a, #1
|
||
jr kp_mskdec
|
||
kp_mskloop:
|
||
add a, a
|
||
kp_mskdec:
|
||
djnz kp_mskloop ; A = 1 << (код&7)
|
||
ld c, a ; C = маска бита
|
||
ld a, e
|
||
rrca
|
||
rrca
|
||
rrca
|
||
and #0x1F ; A = код>>3 (код <= 255)
|
||
ld hl, #__kbdraw_down
|
||
ld d, #0
|
||
ld e, a
|
||
add hl, de
|
||
ld a, (__kbdraw_pending)
|
||
bit 1, a ; _KBDRAW_PEND_EXT?
|
||
jr Z, kp_noext
|
||
ld de, #32 ; +256 кодов = +32 байта карты
|
||
add hl, de
|
||
kp_noext:
|
||
ld a, (__kbdraw_pending)
|
||
bit 0, a ; _KBDRAW_PEND_BREAK?
|
||
jr Z, kp_mkdown
|
||
ld a, c
|
||
cpl ; break → сбросить бит
|
||
and a, (hl)
|
||
jr kp_wr
|
||
kp_mkdown:
|
||
ld a, c ; make → взвести бит
|
||
or a, (hl)
|
||
kp_wr:
|
||
ld (hl), a
|
||
xor a, a
|
||
ld (__kbdraw_pending), a ; префиксы потреблены
|
||
jr kp_next
|
||
|
||
kp_setbrk:
|
||
ld hl, #__kbdraw_pending
|
||
set 0, (hl)
|
||
jr kp_next
|
||
kp_setext:
|
||
ld hl, #__kbdraw_pending
|
||
set 1, (hl)
|
||
kp_next:
|
||
in a, (#_IRQ_SIO_A)
|
||
rrca ; ещё байт в FIFO?
|
||
jp C, kp_drain
|
||
|
||
;; --- FIFO пуст: тот же разбор Rx-overrun, что и в трамплине.
|
||
;; RR1 бит5 = Rx Overrun; без Error Reset (WR0 = 0x30) бит залипнет
|
||
;; и следующий overrun будет неотличим. ---
|
||
ld a, #1
|
||
out (#_IRQ_SIO_A), a ; указатель на RR1
|
||
in a, (#_IRQ_SIO_A) ; читаем RR1 (указатель авто-сброс в 0)
|
||
bit 5, a
|
||
jr Z, kp_noovr
|
||
ld a, #0x30
|
||
out (#_IRQ_SIO_A), a ; WR0 Error Reset
|
||
;; Сброс FSM префиксов — как KBD_Receiver_Overrun у DSS (чистит
|
||
;; KEY_FLG): повисший 0xF0/0xE0 инвертировал бы следующий код.
|
||
xor a, a
|
||
ld (__kbdraw_pending), a
|
||
inc a
|
||
ld (__kbdraw_overrun), a ; снимет kbd_raw_sync()
|
||
kp_noovr:
|
||
pop bc
|
||
pop de
|
||
pop hl
|
||
ei
|
||
ld a, #1
|
||
ret
|
||
__endasm;
|
||
}
|