libc+libbgi: raw-клавиатура (held-state) + column-major блит спрайтов

libc/kbd: kbd_raw_open/close/down — сырой PS/2-канал клавиатуры с
held-state (битовая карта _kbdraw_down[512], EXT-клавиши +256).  Пока
raw открыт, кадровый IRQ-трамплин перехватывает байт SIO у DSS и
декодирует make/break (0xF0/0xE0-префиксы) сам.  FIFO вычерпывается В
ЦИКЛЕ (приёмный буфер SIO 3 байта; пачка break-кодов при одновременном
отпускании иначе теряется → залипание клавиши).  Буфер W2-трамплина
поднят 224→288 Б под выросший обработчик.

libc/conio: kbd_mod_state() — live-состояние модификаторов (ESTEX
CTRLKEY $33h), Shift/Ctrl/Alt/Lock прямо сейчас, KBD_MOD_* маска.

libbgi: gfx_blit_cols(x,y,img,flip) + _bgi_blit_cols_raw — блит
column-major спрайта вертикальным accel-проходом, бесплатный
горизонтальный флип (sstride<0), клип по экрану.  Для персонажей.

libbgi/atlas_load: восстанавливать W3 ДО записи a->count (atlas_t в
--bank памяти резолвится через W3; count оставался мусором).

tests/kbdraw — тест raw-клавиатуры; size-baseline +kbdraw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 17:48:32 +03:00
parent e14b6745f7
commit 484b18d10c
19 changed files with 602 additions and 7 deletions
+9
View File
@@ -86,6 +86,15 @@ void _bgi_blit_rows_raw(const uint8_t *src, uint8_t *dst,
uint8_t w, uint8_t h,
int sstride, uint8_t y0);
/* КОЛОНОЧНАЯ специализация (column-major src -> экран): каждая колонка —
* вертикальный accel-burst (LD A,A, Port_Y авто-шаг); dst +1/колонку, src
* +sstride/колонку. Даёт бесплатный горизонтальный флип (sstride<0 + src
* с конца). w=колонок, h=высота(=размер блока), y0=Port_Y стартовый.
* bgi256/_bgi_blit_cols_raw.c. */
void _bgi_blit_cols_raw(const uint8_t *src, uint8_t *dst,
uint8_t w, uint8_t h,
int sstride, uint8_t y0);
/* Специализация для heal: src == dst == адрес экрана, оба страйда 0
* (строку выбирает Port_Y) — адресной арифметики в цикле нет, строка
* 77Т + burst'ы. y0 — стартовый Port_Y; w,h: 0 = 256. Без скобки и
+84
View File
@@ -0,0 +1,84 @@
/*
* _bgi_blit_cols_raw — accel block-copy КОЛОНКАМИ (буфер column-major ->
* экран). Парный к _bgi_blit_rows_raw, но вертикальный проход: каждая
* КОЛОНКА копируется одним accel-burst'ом вниз (Port_Y авто-шагает),
* опкод LD A,A (0x7F — «копирование блока для вертикального прохода»,
* см. docs/reference/accel_r.txt). dst двигается на +1 (следующий x) на
* колонку; Port_Y сбрасывается в y0 перед каждой колонкой (accel шагает
* его внутри burst'а); src двигается на sstride на колонку.
*
* ЗАЧЕМ: бесплатный ГОРИЗОНТАЛЬНЫЙ флип персонажей — вызывающий подаёт
* src с конца (src+(w-1)*h) и sstride=-h, тогда правая колонка источника
* ложится в левый x экрана (зеркало). Реверс без CPU и без удвоения
* памяти. Спрайты персонажей хранятся column-major.
*
* ВХОД (__sdcccall(1)): src→HL, dst→DE; стек: w 4(ix), h 5(ix),
* sstride 6/7(ix), y0 8(ix). w — число КОЛОНОК (1..256, 0=256); h —
* высота колонки = размер accel-блока (1..256, 0=256); sstride — шаг
* адреса src между колонками (±); y0 — стартовый Port_Y. Callee-pop 5 Б.
*
* Raw: без клиппинга (вызывающий); W3 замаплен (_bgi_begin/end снаружи);
* банк выставлен вызывающим (0x5C прозрачный / 0x50 обычный). src ВНЕ W3.
* ОДИН DI на весь блит. Колонки > 256 строк режет вызывающий (h ≤ 256).
* Клоббер: AF/BC/DE/HL; IX сохраняется.
*/
#include "../_bgi.h"
void _bgi_blit_cols_raw(const uint8_t *src, uint8_t *dst,
uint8_t w, uint8_t h,
int sstride, uint8_t y0) __naked
{
(void)src; (void)dst; (void)w; (void)h; (void)sstride; (void)y0;
__asm
push ix
ld ix, #0
add ix, sp
;; SMC: размер accel-блока <- h (высота колонки)
ld a, 5 (ix)
ld (bc_len_imm), a
;; SMC: src-страйд между колонками (8-бит adc-цепочка, ± на вызов)
ld a, 6 (ix)
ld (bc_slo_imm), a
ld a, 7 (ix)
ld (bc_shi_imm), a
ld b, 4 (ix) ; B = число колонок (0 => 256: djnz)
ld c, 8 (ix) ; C = y0 (Port_Y, константа)
di ; один DI на весь блит
ld d, d ; 0x52 режим размера блока
ld a, #0
bc_len_imm = . - 1
ld b, b ; 0x40 стоп (B=счётчик не тронут)
bc_col:
ld a, c
out (#0x89), a ; Port_Y = y0 (сброс на верх колонки)
;; ЧТЕНИЕ ГОРИЗОНТАЛЬНОЙ копией (LD L,L): src-колонка лежит в ОЗУ
;; СМЕЖНО, адрес шагает (не port_y). ЗАПИСЬ ВЕРТИКАЛЬНОЙ (LD A,A):
;; port_y шагает вниз по видео-колонке. Режим меняется между read и
;; write (do_accel_block применяет один режим на весь блок).
ld l, l ; 0x6D гориз. копия (для ЧТЕНИЯ смежных)
ld a, (hl) ; read-блок: h смежных байт src -> буфер accel
ld a, a ; 0x7F верт. копия (для ЗАПИСИ колонки)
ld (de), a ; write-блок: буфер -> видео-колонка (port_y++)
ld b, b ; 0x40 стоп
;; src += sstride (следующая колонка источника)
ld a, l
add a, #0
bc_slo_imm = . - 1
ld l, a
ld a, h
adc a, #0
bc_shi_imm = . - 1
ld h, a
inc de ; dst += 1 (следующий x)
djnz bc_col
ei ; закрыть DI-окно
pop ix
;; callee-pop 5 байт (w,h,sstride:2,y0)
pop hl ; ret-адрес
pop af
pop af
inc sp
jp (hl)
__endasm;
}
+11 -2
View File
@@ -23,7 +23,7 @@
int atlas_load(atlas_t *a, const char *path)
{
uint8_t *pg = (uint8_t *)0xC000;
uint8_t saved_w3, blk, ok;
uint8_t saved_w3, blk, ok, count;
uint16_t stub;
int fd, n;
@@ -45,8 +45,9 @@ int atlas_load(atlas_t *a, const char *path)
n = read(fd, pg, 16384);
ok = (n >= (int)_ATL_DATA_OFF &&
pg[0] == 'S' && pg[1] == 'P' && pg[2] == 'A' && pg[3] == '1');
count = 0;
if (ok) {
a->count = pg[4];
count = pg[4];
stub = (uint16_t)&_gfx_w0_isr;
pg[0x38] = 0xC3; /* JP _gfx_w0_isr */
pg[0x39] = (uint8_t)(stub & 0xFF);
@@ -55,6 +56,13 @@ int atlas_load(atlas_t *a, const char *path)
pg[0x67] = 0x45;
}
/* Восстановить W3 ДО записи в *a: если atlas_t лежит в банковской
* памяти (её адрес тоже резолвится через W3), запись при ещё
* замапленной странице атласа ушла бы не в тот физический банк
* (найдено 2026-07-15 — PoP room.c держит tile_atlas[] в --bank 1,
* a->count оставался мусором, room_draw читал пиксели не с того
* оффсета). a->blk/a->page выше пишутся ДО ремапа — их это не
* касалось, только a->count. */
sprinter_page_w3(saved_w3);
close(fd);
@@ -64,5 +72,6 @@ int atlas_load(atlas_t *a, const char *path)
errno = EINVAL;
return -1;
}
a->count = count;
return 0;
}
+61
View File
@@ -0,0 +1,61 @@
/*
* gfx_blit_cols — блит спрайта column-major (getimage-заголовок uint16 w,h;
* далее пиксели ПО КОЛОНКАМ: колонка 0 сверху вниз, потом колонка 1, ...)
* в (x,y) текущим банком (gfx_set_bank; 0x5C/0x58 прозрачный). Каждая
* колонка — вертикальный accel-burst (_bgi_blit_cols_raw).
*
* flip != 0 — ГОРИЗОНТАЛЬНОЕ зеркало (для направления персонажа): источник
* читается с последней колонки к первой (sstride отрицательный), правая
* колонка ложится в левый x экрана. Без CPU-реверса и без второй копии
* атласа. Зеркало происходит ВНУТРИ футпринта [x, x+w); чтобы держать
* "hot-point" персонажа на месте (как оригинал: xpos -= w), вызывающий
* при флипе подаёт x = anchor - w (см. pop_kid.c kid_draw).
*
* Клип по экрану ЕСТЬ (аналог _gfx_blit_full, но роли осей зеркальны:
* x = номер КОЛОНКИ, y = смещение ВНУТРИ колонки). sstride считается по
* ПОЛНОЙ высоте (колонки в ОЗУ лежат full_h друг от друга), длина accel-
* блока — по обрезанной. Спрайты Kid < 256 по обеим осям → нарезка на
* полосы >256 не нужна (raw принимает uint8_t w,h; 0 => 256). img вне W3.
*/
#include <stdint.h>
#include "../_bgi.h"
void gfx_blit_cols(int x, int y, const void *img, uint8_t flip)
{
const uint8_t *p = (const uint8_t *)img;
int full_w = p[0] | (p[1] << 8);
int full_h = p[2] | (p[3] << 8);
const uint8_t *px = p + 4;
int w = full_w, h = full_h;
int sstride = flip ? -full_h : full_h; /* шаг между колонками в ОЗУ */
int sy = 0; /* пропуск строк сверху колонки */
int cx = 0; /* пропуск колонок слева экрана */
const uint8_t *src;
uint8_t *dst;
if (full_w <= 0 || full_h <= 0)
return;
/* Клип по экрану — только если хоть один край вылезает. */
if (x < 0 || y < 0 || x + w > _bgi_maxx + 1 || y + h > _bgi_maxy + 1) {
if (x < 0) { cx = -x; w += x; x = 0; }
if (y < 0) { sy = -y; h += y; y = 0; }
if (x + w > _bgi_maxx + 1) w = _bgi_maxx + 1 - x;
if (y + h > _bgi_maxy + 1) h = _bgi_maxy + 1 - y;
if (w <= 0 || h <= 0) return;
}
/* Первая ВИДИМАЯ колонка (с учётом флипа) + первая видимая строка.
* cx экранных колонок слева пропускаем шагом sstride (в флипе он
* отрицателен — уходим к более ранним колонкам источника); sy строк
* сверху — простым +sy внутри колонки (порядок строк флип не меняет). */
src = flip ? (px + (uint16_t)(full_w - 1) * (uint16_t)full_h) : px;
if (cx)
src += cx * sstride;
src += sy;
_bgi_begin();
dst = (uint8_t *)(_gfx_addr_base + (uint16_t)x);
_bgi_blit_cols_raw(src, dst, (uint8_t)w, (uint8_t)h, sstride, (uint8_t)y);
_bgi_end();
}
+7
View File
@@ -93,6 +93,13 @@ void gfx_blit(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);
/* Блит спрайта column-major (пиксели по колонкам) — вертикальным accel-
* проходом. flip!=0 = горизонтальное зеркало (направление персонажа) без
* CPU-реверса/второй копии; зеркало ВНУТРИ футпринта [x,x+w) (для «hot-
* point» подавай x=anchor-w при флипе). Клип по экрану есть.
* Для персонажей (флип); фон — обычный gfx_blit (row-major). */
void gfx_blit_cols(int x, int y, const void *img, uint8_t flip);
/* Восстановить прямоугольник экрана из ОЗУ-копии (стирание спрайта/
* оверлея, нарисованного банком с битом 2: 0x54/0x5C). Всегда
* работает банком GFX_BANK_NORMAL независимо от gfx_set_bank;