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:
@@ -19,7 +19,33 @@
|
||||
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */
|
||||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||||
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида */
|
||||
#include "pop_level.h" /* тип соперника уровня (набор спрайтов) */
|
||||
#include "kid_data.h"
|
||||
#include "pop_guard_pal.h" /* таблица палитр стражей (сгенерена упаковщиком) */
|
||||
|
||||
/* Цвет стража (set_chtab_palette, seg003:257). Оригинал держит ОДИН набор
|
||||
* спрайтов и подменяет 16 цветов палитры chtab_5 по level.guards_color —
|
||||
* так на уровне 2 стражи комнат 4/7/11/15/24 разного цвета (1/3/1/1/6).
|
||||
* Полоса HP стража рисуется тем же атласом, поэтому меняется заодно.
|
||||
* color = 0 — палитру не трогаем (оригинал в этом случае set_chtab_palette
|
||||
* не зовёт: тип уровня не «обычный страж»).
|
||||
* Грузим В ОБЕ палитры: у страниц дабл-буфера они раздельные. */
|
||||
void pop_guard_set_palette(uint8_t color) __banked
|
||||
{
|
||||
/* Таблица — rodata ЭТОГО модуля, то есть страница банка (0xC000+), а
|
||||
* gfx_pal_load отдаёт указатель в BIOS ($A4 через rst #0x08). BIOS
|
||||
* читает только #4000-#BFFF (см. корневой CLAUDE.md), поэтому копируем
|
||||
* запись в локальный буфер — стек гарантированно в W2. Без копии BIOS
|
||||
* забирал мусор с чужой страницы и страж становился невидимым. */
|
||||
uint8_t buf[64];
|
||||
const uint8_t *p;
|
||||
uint8_t i;
|
||||
if (color == 0 || color > POP_GUARD_NPAL) return;
|
||||
p = pop_guard_pal[color - 1];
|
||||
for (i = 0; i < 64; i++) buf[i] = p[i];
|
||||
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
||||
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
||||
}
|
||||
|
||||
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */
|
||||
#define GUARD_PAGES 5
|
||||
@@ -39,6 +65,13 @@ static int sx_l[2], sy_l[2];
|
||||
static uint16_t sw_l[2], sh_l[2];
|
||||
static uint8_t svalid[2];
|
||||
|
||||
/* Брызги удара — тоже свой прямоугольник, по той же причине: спрайт мелкий и
|
||||
* уезжает вперёд-вверх от стража. Живут они ОДИН кадр, но стереть их обязана
|
||||
* та же страница, в которую рисовали (иначе остаток виден через кадр). */
|
||||
static int qx_l[2], qy_l[2];
|
||||
static uint16_t qw_l[2], qh_l[2];
|
||||
static uint8_t qvalid[2];
|
||||
|
||||
/* Ширина спрайта последнего кадра стража — нужна set_char_collision
|
||||
* (char_width_half) при вычислении его ТАЙЛА ОБЪЕКТА. Не по страницам:
|
||||
* это свойство кадра, а не буфера. Отставание на кадр допустимо — так же
|
||||
@@ -47,16 +80,40 @@ static uint16_t g_last_w;
|
||||
|
||||
uint16_t pop_guard_fp_width(void) __banked { return g_last_w; }
|
||||
|
||||
/* Набор спрайтов соперника выбирает ТИП УРОВНЯ (load_lev_spr, seg000:1092):
|
||||
* tbl_guard_type = {0 guard, 1 fat, 2 skeleton, 3 vizier, 4 shadow, −1 нет}.
|
||||
* У нас пока два набора: обычный страж (уровни 1-2) и скелет (уровень 3);
|
||||
* у скелета и кадров меньше (28 против 34), поэтому пятую страницу он не
|
||||
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
|
||||
int pop_guard_load(void) __banked
|
||||
{
|
||||
static const char *const names[GUARD_PAGES] = {
|
||||
static const char *const guard_names[GUARD_PAGES] = {
|
||||
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
|
||||
"GUARD\\g3.atl", "GUARD\\g4.atl"
|
||||
};
|
||||
static const char *const skel_names[GUARD_PAGES] = {
|
||||
"SKEL\\g0.atl", "SKEL\\g1.atl", "SKEL\\g2.atl",
|
||||
"SKEL\\g3.atl", 0
|
||||
};
|
||||
const char *const *names;
|
||||
int8_t type = pop_level_guard_type(pop_current_level);
|
||||
uint8_t i;
|
||||
for (i = 0; i < GUARD_PAGES; i++)
|
||||
if (type < 0) return 0; /* соперников на уровне нет */
|
||||
names = (type == 2) ? skel_names : guard_names;
|
||||
for (i = 0; i < GUARD_PAGES; i++) {
|
||||
if (names[i] == 0) break;
|
||||
if (atlas_load(&gp[i], names[i]) != 0) return -1;
|
||||
}
|
||||
g_ok = 1;
|
||||
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
|
||||
* из res10.bin по guards_color — это делает pop_guard_set_palette при
|
||||
* входе в комнату (seg002:186: у не-guard curr_guard_color = 0). */
|
||||
if (type == 2) {
|
||||
uint8_t buf[64], k;
|
||||
for (k = 0; k < 64; k++) buf[k] = pop_skel_pal[k]; /* BIOS: буфер в W2 */
|
||||
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
||||
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -77,7 +134,8 @@ void pop_guard_heal(void) __banked
|
||||
uint8_t p = gfx_get_draw_page() & 1;
|
||||
if (gvalid[p]) pop_heal_fast(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
|
||||
if (svalid[p]) pop_heal_fast(sx_l[p], sy_l[p] + POP_YOFF, sw_l[p], sh_l[p]);
|
||||
gvalid[p] = svalid[p] = 0;
|
||||
if (qvalid[p]) pop_heal_fast(qx_l[p], qy_l[p] + POP_YOFF, qw_l[p], qh_l[p]);
|
||||
gvalid[p] = svalid[p] = qvalid[p] = 0;
|
||||
}
|
||||
|
||||
void pop_guard_draw(void) __banked
|
||||
@@ -109,19 +167,93 @@ void pop_guard_draw(void) __banked
|
||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
if (w && h) {
|
||||
int bx = obj_x;
|
||||
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507:
|
||||
* obj_clip_top = 0, obj_clip_bottom = 192). Персонаж, вышедший за
|
||||
* него (скелет, падающий в пропасть), обязан обрезаться: ниже поля
|
||||
* идёт полоса HP, и без клипа спрайт лез прямо на неё. */
|
||||
int skip = 0, rows;
|
||||
top = obj_y - (int)h + 1;
|
||||
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
|
||||
if (flip) bx -= (int)w;
|
||||
top = obj_y - (int)h + 1;
|
||||
p = gfx_get_draw_page() & 1;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip, 0, 0);
|
||||
else
|
||||
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;
|
||||
rows = (int)h;
|
||||
if (top < 0) { skip = -top; rows -= skip; top = 0; }
|
||||
if (top + rows > 192) rows = 192 - top;
|
||||
if (rows > 0) {
|
||||
p = gfx_get_draw_page() & 1;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
|
||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip,
|
||||
(uint8_t)skip, (uint8_t)rows);
|
||||
else
|
||||
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, skip, rows);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* heal — ровно по НАРИСОВАННОМУ (обрезанному) прямоугольнику. */
|
||||
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = (uint16_t)rows;
|
||||
gvalid[p] = 1;
|
||||
clip_x = bx; clip_y = top; clip_w = w; clip_h = (uint16_t)rows;
|
||||
}
|
||||
g_last_w = w; /* для char_width_half, см. выше */
|
||||
clip_x = bx; clip_y = top; clip_w = w; clip_h = h;
|
||||
}
|
||||
/* Брызги удара (draw_hurt_splash, seg006:16CE) — ровно здесь, между
|
||||
* стражем и клинком: draw_guard кладёт их в objtable СРАЗУ после стража
|
||||
* и ДО клинка (seg008:22C9), а порядок записей задаёт порядок рисования.
|
||||
*
|
||||
* Спрайт — chtab_5_guard `obj_id = 1`, то есть image 1 нашего атласа
|
||||
* стража (страница 0, индекс 1): у add_midtable аргумент `obj_id + 1`, а
|
||||
* get_image вычитает единицу обратно (тот же off-by-one, что расписан в
|
||||
* шапке pop_pack_guard.py). У Кида это image 218 своего chtab.
|
||||
*
|
||||
* Смещения от кадра персонажа — по трём веткам оригинала; у стража
|
||||
* подъём на 11 (у Кида 15: `((charid == kid) << 2) + 11`). */
|
||||
if (pop_guard_hurt && Guard.frame != 178 /* chomped: брызг нет */) {
|
||||
int qx = fp_x, qy = obj_y;
|
||||
const uint8_t *qimg = (const uint8_t *)atlas_image(&gp[0], 1);
|
||||
uint16_t qw, qh;
|
||||
int fw = (Guard.direction < 0) ? -5 : 5; /* obj_dx_forward(5) */
|
||||
if (Guard.frame == 185 || /* мёртв */
|
||||
(Guard.frame >= 106 && Guard.frame < 111)) { /* падение+посадка */
|
||||
qy += 4;
|
||||
} else if (Guard.frame == 177) { /* напоролся на пики */
|
||||
fw = -fw; /* obj_dx_forward(-5) */
|
||||
} else {
|
||||
qy -= 11;
|
||||
}
|
||||
qx = (qx + fw) * 8 / 7; /* calc_screen_x_coord */
|
||||
gfx_w0_map(gp[0].page);
|
||||
qw = (uint16_t)(qimg[0] | ((uint16_t)qimg[1] << 8));
|
||||
qh = (uint16_t)(qimg[2] | ((uint16_t)qimg[3] << 8));
|
||||
if (qw && qh) {
|
||||
int qtop = qy - (int)qh + 1;
|
||||
uint8_t qflip = (uint8_t)(Guard.direction >= 0);
|
||||
int qskip = 0, qrows = (int)qh; /* клип полем 192, как у спрайта */
|
||||
if (qflip) qx -= (int)qw;
|
||||
if (qtop < 0) { qskip = -qtop; qrows -= qskip; qtop = 0; }
|
||||
if (qtop + qrows > 192) qrows = 192 - qtop;
|
||||
if (qrows <= 0) goto splash_done;
|
||||
qh = (uint16_t)qrows;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
if (pop_onscreen_cols(qx, qtop + POP_YOFF, qw, qh))
|
||||
gfx_blit_cols_part_noclip(qx, qtop + POP_YOFF, qimg, qflip,
|
||||
(uint8_t)qskip, (uint8_t)qrows);
|
||||
else
|
||||
gfx_blit_cols_part(qx, qtop + POP_YOFF, qimg, qflip, qskip, qrows);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
p = gfx_get_draw_page() & 1;
|
||||
qx_l[p] = qx; qy_l[p] = qtop; qw_l[p] = qw; qh_l[p] = qh;
|
||||
qvalid[p] = 1;
|
||||
if (!clip_w) {
|
||||
clip_x = qx; clip_y = qtop; clip_w = qw; clip_h = qh;
|
||||
} else {
|
||||
int x1 = clip_x + (int)clip_w, y1 = clip_y + (int)clip_h;
|
||||
if (qx < clip_x) clip_x = qx;
|
||||
if (qtop < clip_y) clip_y = qtop;
|
||||
if (qx + (int)qw > x1) x1 = qx + (int)qw;
|
||||
if (qtop + (int)qh > y1) y1 = qtop + (int)qh;
|
||||
clip_w = (uint16_t)(x1 - clip_x);
|
||||
clip_h = (uint16_t)(y1 - clip_y);
|
||||
}
|
||||
}
|
||||
splash_done: ;
|
||||
}
|
||||
/* Клинок стража — тот же chtab_0, что у Кида (add_sword_to_objtable,
|
||||
* seg006:1798): у ЖИВОГО стража меч в руке всегда. Свой прямоугольник
|
||||
@@ -157,7 +289,9 @@ void pop_guard_draw(void) __banked
|
||||
* (seg008:690 draw_tile_fore). Без этого страж виден сквозь передний
|
||||
* столб. */
|
||||
if (w && h)
|
||||
pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction, Guard.sword);
|
||||
pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction, Guard.sword,
|
||||
clip_x, clip_y + POP_YOFF, clip_w, clip_h,
|
||||
Guard.action, Guard.curr_col, Guard.curr_row);
|
||||
}
|
||||
|
||||
/* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- *
|
||||
|
||||
Reference in New Issue
Block a user