PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат
Скелет (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>
This commit is contained in:
@@ -71,7 +71,8 @@ uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
|
||||
if (!sword_ok) return 0;
|
||||
if (!((ch->frame >= 229 && ch->frame < 238) ||
|
||||
ch->sword != 0 ||
|
||||
(ch->charid == CHARID_2_GUARD && ch->alive < 0)))
|
||||
((ch->charid == CHARID_2_GUARD || ch->charid == CHARID_4_SKELETON) &&
|
||||
ch->alive < 0)))
|
||||
return 0;
|
||||
sf = (uint8_t)(fr->sword & 0x3F);
|
||||
if (sf == 0 || sf >= KID_NSWORD) return 0;
|
||||
@@ -88,15 +89,25 @@ uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
|
||||
bx = obj_x + ((ch->direction < 0) ? -(int)SWORD_DX[sf] : (int)SWORD_DX[sf]);
|
||||
top = obj_y + (int)SWORD_DY[sf] - (int)h + 1;
|
||||
if (flip) bx -= (int)w;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
/* Клинок почти всегда целиком на экране — noclip-путь (см. gfx.h:
|
||||
* подготовка клипающего варианта стоит ~5.6 К тактов на вызов). */
|
||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, simg, flip, 0, 0);
|
||||
else
|
||||
gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, 0, 0);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
*rx = bx; *ry = top; *rw = w; *rh = h;
|
||||
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507): клинок
|
||||
* обрезается по нему так же, как сам персонаж. Без этого у падающего
|
||||
* в пропасть скелета меч оставался виден НИЖЕ поля, на полосе HP. */
|
||||
{
|
||||
int skip = 0, rows = (int)h;
|
||||
if (top < 0) { skip = -top; rows -= skip; top = 0; }
|
||||
if (top + rows > 192) rows = 192 - top;
|
||||
if (rows <= 0) return 0;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
/* Клинок почти всегда целиком на экране — noclip-путь (см. gfx.h:
|
||||
* подготовка клипающего варианта стоит ~5.6 К тактов на вызов). */
|
||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
|
||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, simg, flip,
|
||||
(uint8_t)skip, (uint8_t)rows);
|
||||
else
|
||||
gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, skip, rows);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
*rx = bx; *ry = top; *rw = w; *rh = (uint16_t)rows;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -214,34 +225,18 @@ pop_char_t Char;
|
||||
* ними: для стража Opp = Kid, для Кида Opp = Guard. */
|
||||
pop_char_t Opp;
|
||||
|
||||
/* Снимок последовательности на момент входа в окно Char: по нему
|
||||
* pop_savekid_state отличает «последовательность сменил ДИСПЕТЧЕР (в Char)»
|
||||
* от «её сменил pop_map (прямо в Kid)». */
|
||||
static uint16_t kid_seq_at_load;
|
||||
|
||||
void pop_loadkid(void) { Char = Kid; kid_seq_at_load = Kid.curr_seq; }
|
||||
void pop_loadkid(void) { Char = Kid; }
|
||||
void pop_savekid(void) { Kid = Char; }
|
||||
/* Выгрузка после control(): копируем ТОЛЬКО то, что диспетчер реально
|
||||
* меняет у активного персонажа — последовательность и состояние меча.
|
||||
/* Выгрузка после control() — теперь ПОЛНАЯ, как savekid оригинала.
|
||||
*
|
||||
* Почему не `Kid = Char`, как делает savekid оригинала: у нас часть
|
||||
* действий control() исполняет pop_map (pop_down_action, pop_jump_up_seq,
|
||||
* safe_step...), и он пишет ПРЯМО в Kid — там ещё не портирован переход на
|
||||
* Char. Полное копирование затирало эти правки: Kid терял выравнивание x
|
||||
* и ряд, и спуск с уступа через вис не срабатывал — персонаж просто
|
||||
* приседал. Когда pop_map переведём на Char, здесь вернётся Kid = Char. */
|
||||
void pop_savekid_state(void)
|
||||
{
|
||||
/* Последовательность мог сменить КАЖДЫЙ из двоих: диспетчер — в Char
|
||||
* (pop_char_set_seq), pop_map — прямо в Kid (kid_set_seq: спуск с
|
||||
* уступа SEQ_68, зацеп, осторожный шаг). Решение pop_map старше:
|
||||
* диспетчер про эти переходы не знает и, копируя Char вслепую, затирал
|
||||
* их — спуск в проём не срабатывал, а персонаж даже не приседал, потому
|
||||
* что pop_down_action уже вернул «обработано». */
|
||||
uint8_t map_changed = (uint8_t)(Kid.curr_seq != kid_seq_at_load);
|
||||
if (!map_changed) Kid.curr_seq = Char.curr_seq;
|
||||
Kid.sword = Char.sword;
|
||||
}
|
||||
* Была выборочной (только curr_seq и sword) ровно потому, что pop_map правил
|
||||
* геометрию ПРЯМО в Kid, мимо окна Char: полное копирование затирало его
|
||||
* правки — Kid терял выравнивание x и ряд, и спуск с уступа через вис не
|
||||
* срабатывал (персонаж просто приседал). Приходилось ещё и сравнивать
|
||||
* последовательность со снимком на входе в окно, чтобы понять, чья правка
|
||||
* свежее. С переводом физики на Char (GUARD-PHYS) диспетчер и pop_map
|
||||
* пишут в одно и то же место, и разбирать это больше не нужно. */
|
||||
void pop_savekid_state(void) { Kid = Char; }
|
||||
void pop_loadshad(void) { Char = Guard; }
|
||||
void pop_saveshad(void) { Guard = Char; }
|
||||
void pop_loadshad_and_opp(void) { Char = Guard; Opp = Kid; }
|
||||
@@ -323,9 +318,20 @@ static void load_frame(void)
|
||||
{
|
||||
/* Таблица кадров зависит от персонажа (seg006:0293): у Kid своя, у
|
||||
* стража frame_tbl_guard с индексом frame + add_frame − 149, где
|
||||
* add_frame = 70 для кадров 102..106 (seg006:535). */
|
||||
* add_frame = 70 для кадров 102..106 (seg006:535).
|
||||
*
|
||||
* СКЕЛЕТ идёт по той же ветке, что страж (seg006:529 — общий case): у
|
||||
* него свой атлас, но НУМЕРАЦИЯ кадров та же. Пока здесь стоял только
|
||||
* charid_2_guard, скелет попадал в таблицу Кида, получал несуществующий
|
||||
* image (180 при 28 спрайтах в наборе) и не рисовался вовсе.
|
||||
* ТЕНЬ (charid_1_shadow) — по таблице стража ТОЛЬКО в кадрах 150..189,
|
||||
* в остальных она ходит кадрами Кида (seg006:532). */
|
||||
const uint8_t *f;
|
||||
if (Char.charid == CHARID_2_GUARD) {
|
||||
uint8_t use_guard_tbl =
|
||||
(Char.charid == CHARID_2_GUARD || Char.charid == CHARID_4_SKELETON ||
|
||||
(Char.charid == CHARID_1_SHADOW &&
|
||||
Char.frame >= 150 && Char.frame < 190));
|
||||
if (use_guard_tbl) {
|
||||
int16_t idx = (int16_t)Char.frame;
|
||||
if (idx >= 102 && idx < 107) idx += 70;
|
||||
idx -= 149;
|
||||
@@ -473,21 +479,18 @@ void play_seq(void)
|
||||
* check_jump_up уходил в «пустой прыжок вверх» вместо «шаг назад и
|
||||
* зацеп». Результат менялся от кадра к кадру — вместе с анимацией стража.
|
||||
*
|
||||
* determine_col у нас существует только для Кида: pop_map работает с Kid
|
||||
* напрямую, а не с абстрактным Char. Поэтому вторую половину связки делаем
|
||||
* только на его ветке — стражу пока хватает перезагрузки самого кадра (его
|
||||
* колонку ведёт pop_guard_enter/leave). Char.curr_col синхронизируем
|
||||
* руками: окно Char — копия Kid. */
|
||||
* determine_col теперь общий: pop_map работает с Char, поэтому колонка
|
||||
* пересчитывается ЛЮБОМУ активному персонажу — ровно как load_fram_det_col
|
||||
* в оригинале. Раньше вторая половина связки работала только на ветке Кида
|
||||
* (стражу колонку вёл pop_guard_enter/leave) — прямое следствие того, что
|
||||
* физика знала один Kid. */
|
||||
void pop_load_fram_det_col(void)
|
||||
{
|
||||
if (!kdat_ok) return;
|
||||
gfx_w0_map(kdat_page);
|
||||
load_frame();
|
||||
gfx_w0_unmap();
|
||||
if (Char.charid == CHARID_0_KID) {
|
||||
pop_determine_col();
|
||||
Char.curr_col = Kid.curr_col;
|
||||
}
|
||||
pop_determine_col();
|
||||
}
|
||||
|
||||
/* Прокрутить последовательность КОНКРЕТНОГО персонажа (порт связки
|
||||
@@ -728,7 +731,14 @@ void kid_draw(void)
|
||||
uint8_t dp = gfx_get_draw_page(); /* в какую страницу рисуем */
|
||||
/* clip_char (seg006:1749): тайл над головой = стена/пол → всё выше
|
||||
* линии полосы своего ряда НЕ рисуется. Иначе Kid просвечивает
|
||||
* сквозь щели кладки при спуске/висе. */
|
||||
* сквозь щели кладки при спуске/висе.
|
||||
*
|
||||
* Окно Char обязательно: clip_char работает с АКТИВНЫМ персонажем
|
||||
* (в оригинале он и зовётся внутри add_kid_to_objtable, то есть под
|
||||
* loadkid), а к моменту отрисовки в Char лежит тот, кто тикал
|
||||
* последним — страж. Выгружать назад нечего: clip_char только
|
||||
* читает. */
|
||||
pop_loadkid();
|
||||
ct = pop_clip_char_top(kid_fp_x, obj_y, w, h);
|
||||
if (ct > top) {
|
||||
skip = ct - top;
|
||||
|
||||
Reference in New Issue
Block a user