L1: фиксы прогона уровня 1 — уровень мутабелен, стражи, loose-плиты, порядок

Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём.  Разбор каждого — bug_closed.md.

Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
  рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
  (POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
  стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
  роняло Кида сквозь пол.

Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
  каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
  ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
  (redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.

По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
  1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
     нарисовался поверх передних столбов (kid_fore_clip_restore);
  2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
     get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
     настоящей X.  Теперь колонка выводится из X, как в оригинале;
  3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
     «упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
     габарита, поэтому бегущий Кид уходит за объекты справа.  Считается
     для обоих персонажей — enter_guard ставит action=1 и стражу.

Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража.  make size-check — роста нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-03 22:35:41 +03:00
parent 1f16e8fa70
commit b5d2a81ee3
21 changed files with 1070 additions and 164 deletions
+77 -4
View File
@@ -922,6 +922,35 @@ void pop_leveldoor_redraw(int row, int col) __banked
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Ворота ВНУТРИ комнаты: перерисовать бары по живому modif (порт draw_trob,
* seg007:01E6 — им заканчивается animate_door). (row,col) — сами ворота;
* решётку рисует ячейка ПРАВОГО соседа (draw_tile, ветка lcode==4), поэтому
* wipe и redraw идут по ней. Логика ровно как у левого шва
* (pop_room_redraw_seam_left), только колонка произвольная.
*
* Bar-в-чёрный обязателен: бары висят в видео-ОЗУ и ОЗУ-копии, и при
* ПОДЪЁМЕ решётки heal вернул бы запечённые нижние бары. Полоса — ровно
* зона решётки: от gate_top_y (dby62 = 63*row+3) до низа (63*row+64);
* выше начинается ряд −1, ниже — стена ряда ниже.
*
* Ворота в col9: их бары рисуются уже в СОСЕДНЕЙ комнате, в отрисованной их
* не видно — оригинал такой trob тоже пропускает
* (get_trob_right_pos_in_drawn_room → 30). Для взгляда из комнаты справа
* работает pop_room_redraw_seam_left. */
void pop_gate_redraw(int row, int col) __banked
{
int x, yt;
if (col + 1 > 9 || row < 0 || row > 2) return;
x = COL_XH[col + 1] * 8;
yt = POP_YOFF + 63 * row;
gfx_set_bank(GFX_BANK_NORMAL);
setfillstyle(SOLID_FILL, BLACK);
bar(x, yt + 3, x + 31, yt + 64);
gfx_set_bank(GFX_BANK_TRANSPARENT);
draw_tile(row, col + 1);
gfx_set_bank(GFX_BANK_NORMAL);
}
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
@@ -986,6 +1015,7 @@ typedef struct {
int prev_y[2]; /* последняя нарисованная y на КАЖДОЙ странице (heal) */
int8_t speed;
int8_t row, col; /* тайл-позиция куска (для окклюзии соседа) */
uint8_t room; /* curmob.room: кусок падает В СВОЕЙ комнате, не в текущей */
} mob_t;
static mob_t mobs[MOB_MAX];
@@ -1014,6 +1044,7 @@ void pop_loose_mob_spawn(int row, int col) __banked
m->y = MOB_Y_LAND[row + 1]; /* y_loose_land: низ куска в момент отрыва */
m->row = (int8_t)row;
m->col = (int8_t)col;
m->room = g_room; /* curmob.room (add_mob, seg007:1010) */
m->speed = 0;
m->active = 1;
m->clean = 0;
@@ -1047,14 +1078,42 @@ void pop_loose_mob_reset(void) __banked
}
}
/* Смена комнаты. Куски НЕ останавливаем: в оригинале mobs[] чистится только
* в start_level (seg003:88), а do_mobs (seg007:1063) крутит их все независимо
* от drawn_room — move_loose работает по curmob.room, и плита, за которой Кид
* не досмотрел, всё равно долетает и кладёт щебень (BUG-LOOSE-2: в комнате 12
* плита 0,1 не успевала упасть до ухода в комнату 15). Сбросить надо только
* heal-историю: новая комната перерисована целиком, старых пикселей нет. */
void pop_loose_mob_room_changed(void) __banked
{
uint8_t i;
for (i = 0; i < MOB_MAX; i++) {
mobs[i].defer = 0;
mobs[i].prev_y[0] = mobs[i].prev_y[1] = MOB_Y_NONE;
/* дочистка хвоста относилась к покинутой комнате — она уже не нужна */
if (!mobs[i].active) mobs[i].clean = 0;
}
}
/* Тайл под куском. В своей комнате — обычный tile_code (он знает про кромки
* и соседей), в чужой — прямо из данных уровня. */
static uint8_t mob_tile_at(mob_t *m)
{
if (m->room == g_room) return (uint8_t)(tile_code(m->row, m->col) & 0x1F);
if (m->row < 0 || m->row > 2 || m->col < 0 || m->col > 9) return 1; /* кромка = пол */
return (uint8_t)(pop_level_tile(m->room, (uint8_t)(m->row * 10 + m->col)) & 0x1F);
}
static void mob_tick_one(mob_t *m, uint8_t pg)
{
uint8_t here;
if (!m->active && !m->clean) return;
here = (uint8_t)(m->room == g_room);
/* Стереть кусок, нарисованный на ЭТОЙ странице в прошлый раз (у каждой
* страницы своя копия фона). Раньше коридор был фиксирован под ряд 2;
* плита-потолок падает через весь ряд 0, поэтому чистим по фактической
* прошлой позиции. */
if (m->prev_y[pg] != MOB_Y_NONE) {
if (here && m->prev_y[pg] != MOB_Y_NONE) {
heal_off(MOB_X0(m->x), m->prev_y[pg] - 27, MOB_W, 32);
m->prev_y[pg] = MOB_Y_NONE;
}
@@ -1067,9 +1126,17 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
* loose_fall «плита ломает плиту» не нужен: в ряду ниже её нет);
* пол — приземление (loose_land: debris + do_knock). */
if (m->row < 3 && m->y < 226 && MOB_Y_BOUND[m->row + 1] <= m->y) {
uint8_t t = (uint8_t)(tile_code(m->row, m->col) & 0x1F);
uint8_t t = mob_tile_at(m);
if (t == 0 || t == 11) {
m->row++; /* mob_down_a_row */
} else if (!here) {
/* Приземление в ПОКИНУТОЙ комнате: рисовать нечего, сигнальная
* пара pop_loose_landed/pop_debris_at обслуживает только текущую
* комнату — щебень кладём прямо в данные уровня (loose_land пишет
* в curr_room_tiles комнаты curmob.room, а не отрисованной). */
pop_level_set_tile(m->room, (uint8_t)(m->row * 10 + m->col), 0x0E);
m->active = 0; m->clean = 0;
return;
} else if (pop_loose_landed) {
/* сигнал прошлого приземления ещё не разобран (один за кадр) —
* подержим кусок на границе и приземлим следующим кадром */
@@ -1082,9 +1149,10 @@ static void mob_tick_one(mob_t *m, uint8_t pg)
}
}
if (m->y - 27 >= POP_PLAYFIELD_H) { /* весь ниже поля (улетел из комнаты) */
m->active = 0; m->clean = 2;
m->active = 0; m->clean = (uint8_t)(here ? 2 : 0);
return;
}
if (!here) return; /* чужая комната — считаем, но не рисуем */
/* Порядок «плита vs Kid». В оригинале объекты рисуются при обходе СВОИХ
* тайлов (redraw_needed_tiles: ряды 2,1,0; колонки 0..9), и только внутри
* ОДНОГО тайла сортируются по obj_y (sort_curr_objs, seg008:1561: кто
@@ -1165,8 +1233,13 @@ void pop_loose_mob_draw_over(void) __banked
uint8_t pop_loose_mob_pos(int *col, int *y) __banked
{
uint8_t i;
/* check_loose_fall_on_kid (seg007:1192) начинается с Char.room ==
* curmob.room: кусок, падающий в покинутой комнате, Киду по голове не
* бьёт. */
for (i = 0; i < MOB_MAX; i++)
if (mobs[i].active) { *col = mobs[i].col; *y = mobs[i].y; return 1; }
if (mobs[i].active && mobs[i].room == g_room) {
*col = mobs[i].col; *y = mobs[i].y; return 1;
}
return 0;
}