diff --git a/applications/PoP/docs/layout_plan_v2.md b/applications/PoP/docs/layout_plan_v2.md index 4ff82cf..2fb5e2b 100644 --- a/applications/PoP/docs/layout_plan_v2.md +++ b/applications/PoP/docs/layout_plan_v2.md @@ -18,7 +18,7 @@ | 1. `kid_data.h` → EMM-страница + `load_frame`/`cur_frame` | **сделан** | `_CODE` −3 247 Б; попутно найден и обойдён баг кодогенерации SDCC (см. ниже) | | 2. `pop_geom.c` (дедуп геометрии + PRNG) | **сделан** | −41 Б `_CODE`, −11 Б W3; ценность — не байты, а bank-safe слой | | 3. `pop_map` без вызовов графики | **сделан** (фазы 1a/1b) | через пометки перерисовки, см. ниже | -| 4. Разгрузка/перебалансировка W3 | **сделан** | в резиденте остался ТОЛЬКО `pop_bg`; W3 14 376 → 11 632 (свободно 4 752 Б) | +| 4. Разгрузка/перебалансировка W3 | **сделан** | резидент = `pop_bg` + `pop_gdraw` (отрисовка стража, 2026-07-29); W3 14 376 → 12 819 (свободно 3 565 Б) | | 5. Данные (`room_modif`, `dl1/dl2`, `_kbdraw_down`) | не начат | потенциал ~1.5 КБ | | 6. Контракт банка стражей + пробник | **пробник сделан** | `tests/w3bankgfx` — модель подтверждена в MAME, см. ниже | | 7. Дедуп семейства `draw_tile` | отложен по решению пользователя | «мороки много, выгода не так велика» | @@ -27,6 +27,15 @@ W3-резидент 14 376 → 11 632 (свободно 2 008 → 4 752 Б). Сумма кода упала на ~3.2 КБ (данные Kid уехали в EMM), остальное — перераспределение. +**Замер 2026-07-29 (после стража).** Появление стража съело кучу до 805 Б; +разгрузка — вынос ОТРИСОВКИ стража в резидент (`pop_gdraw.c`, `--w3`), логика +и состояние остались в W1/W2, чтобы банк `guards.c` их видел (R2). Итог: +`_CODE` 26 149 → 25 703, куча **805 → 1 245 Б**, W3-резидент 11 656 → 12 819 +(свободно 4 728 → 3 565 Б), банк 1 — 236 / 16 384 Б. Граница «что резидент» +теперь формулируется одним правилом: **резидент = только то, что рисует и +зовётся исключительно из главного цикла**; всё, что может понадобиться банку, +остаётся в W1/W2. + ### Что сделано вместо §5.3 (вынос loose в W3) Вместо переноса кода между окнами выбран (по обсуждению с пользователем) diff --git a/applications/PoP/poc/res/guard/g0.atl b/applications/PoP/poc/res/guard/g0.atl index 87ab285..4041d2c 100644 Binary files a/applications/PoP/poc/res/guard/g0.atl and b/applications/PoP/poc/res/guard/g0.atl differ diff --git a/applications/PoP/poc/res/guard/g1.atl b/applications/PoP/poc/res/guard/g1.atl index db8440b..04b3da3 100644 Binary files a/applications/PoP/poc/res/guard/g1.atl and b/applications/PoP/poc/res/guard/g1.atl differ diff --git a/applications/PoP/poc/res/guard/g2.atl b/applications/PoP/poc/res/guard/g2.atl index cd1d58d..5bf728c 100644 Binary files a/applications/PoP/poc/res/guard/g2.atl and b/applications/PoP/poc/res/guard/g2.atl differ diff --git a/applications/PoP/poc/res/guard/g3.atl b/applications/PoP/poc/res/guard/g3.atl index c7eeae7..3f8bc15 100644 Binary files a/applications/PoP/poc/res/guard/g3.atl and b/applications/PoP/poc/res/guard/g3.atl differ diff --git a/applications/PoP/poc/res/guard/g4.atl b/applications/PoP/poc/res/guard/g4.atl index 8f8cae0..25e914a 100644 Binary files a/applications/PoP/poc/res/guard/g4.atl and b/applications/PoP/poc/res/guard/g4.atl differ diff --git a/applications/PoP/roomtest/Makefile b/applications/PoP/roomtest/Makefile index 7ed1b91..e548395 100644 --- a/applications/PoP/roomtest/Makefile +++ b/applications/PoP/roomtest/Makefile @@ -6,12 +6,13 @@ EXAMPLE := roomtest # small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе # CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge). MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи) -# --w3 берёт ОДИН файл на флаг. В W3-резиденте держим ТОЛЬКО pop_bg — -# отрисовку тайлов: она большая и её зовёт лишь главный цикл. Всё -# остальное живёт в W1/W2, чтобы оставаться достижимым из будущих -# __banked-модулей (стражи): из банка резидентная страница W3 не видна — -# ни напрямую, ни транзитивно. См. docs/layout_plan_v2.md §2 (R2), §4. -EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --bank 1=guards.c +# --w3 берёт ОДИН файл на флаг (флагов можно несколько). В W3-резиденте +# держим ТОЛЬКО отрисовку, которую зовёт лишь главный цикл: pop_bg (тайлы) +# и pop_gdraw (спрайт стража). Всё остальное живёт в W1/W2, чтобы +# оставаться достижимым из __banked-модулей (guards.c): из банка +# резидентная страница W3 не видна — ни напрямую, ни транзитивно. +# См. docs/layout_plan_v2.md §2 (R2), §4. +EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --w3 pop_gdraw.c --bank 1=guards.c EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c BG_DIR := $(CURDIR)/../poc/res/bg @@ -62,4 +63,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py cd $(TC) && python3 pop_extract_kid_data.py -$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) +$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_gdraw.c pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) diff --git a/applications/PoP/roomtest/pop_bg.c b/applications/PoP/roomtest/pop_bg.c index 3bd60c5..594e686 100644 --- a/applications/PoP/roomtest/pop_bg.c +++ b/applications/PoP/roomtest/pop_bg.c @@ -1325,13 +1325,20 @@ static void other_overlay_tile(int row, int col) } } -void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, - int8_t dir, uint8_t frame, uint8_t action) +/* Футпринт персонажа в тайлах (порт set_char_collision, seg006:0723). + * Выделено из pop_fore_over_kid, потому что тем же счётом пользуется страж + * (pop_fore_over_char): в оригинале это общий код для любого Char. + * Результат — в файловых статиках, а не через 6 указателей: на Z80 такой + * возврат стоил бы дороже самой функции. */ +static int8_t fp_cL, fp_cR, fp_cLraw, fp_rT, fp_rB, fp_rTraw; + +static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h, + int8_t dir) { int wh = (int)((w + 1) >> 1); /* char_width_half */ int xl = obj_x / 2 + 58; /* char_x_left (58-база) */ int xr; - int8_t cL, cR, cLraw, rT, rB, r, c, rTraw; + int8_t cL, cR, cLraw, rT, rB, rTraw; if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */ xr = xl + wh; cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */ @@ -1359,6 +1366,17 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, * (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим * включение ряда rB-1. */ if (rB > 0 && rT >= rB) rT = rB - 1; + fp_cL = cL; fp_cR = cR; fp_cLraw = cLraw; + fp_rT = rT; fp_rB = rB; fp_rTraw = rTraw; +} + +void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, + int8_t dir, uint8_t frame, uint8_t action) +{ + int8_t cL, cR, cLraw, rT, rB, rTraw, r, c; + char_footprint(obj_x, obj_y, w, h, dir); + cL = fp_cL; cR = fp_cR; cLraw = fp_cLraw; + rT = fp_rT; rB = fp_rB; rTraw = fp_rTraw; /* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт * Kid попадает в midtable — база порядка окклюзии в other_overlay_tile. * run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/ @@ -1427,3 +1445,33 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, } gfx_set_bank(GFX_BANK_NORMAL); } + +/* Передние грани тайлов поверх ПРОИЗВОЛЬНОГО персонажа (страж, дальше — + * тень/скелет). В оригинале любой Char — запись midtable, а foretable + * рисуется после всех midtable (draw_tile_fore, seg008:690), поэтому + * колонна и кромка пола перекрывают всех одинаково. + * + * Отличие от pop_fore_over_kid: только слой fore (+ полоса потолка). + * Оверлеи тела пола (draw_other_overlay) завязаны на позы Kid — вис, + * полёт, подъём, — которых у стража нет: он ходит и дерётся на полу + * (seq_77..seq_84). Появятся падающие/висящие стражи — тогда и общий + * порт redraw_at_char2, а не догадки сейчас. */ +void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h, + int8_t dir) +{ + int8_t r, c; + char_footprint(obj_x, obj_y, w, h, dir); + gfx_set_bank(GFX_BANK_SPRITE); + for (r = fp_rT; r <= fp_rB; r++) + for (c = fp_cL; c <= fp_cR; c++) + fore_tile(r, c); + if (fp_rTraw < 0) { + g_clip_top = POP_YOFF; + for (c = fp_cL; c <= fp_cR; c++) { + ov_mark(COL_XH[c] * 8, 0, 64, CEIL_BAND_H); + ceil_over_kid_tile(c); + } + g_clip_top = 0; + } + gfx_set_bank(GFX_BANK_NORMAL); +} diff --git a/applications/PoP/roomtest/pop_bg.h b/applications/PoP/roomtest/pop_bg.h index 0ffb5b8..b96c5c3 100644 --- a/applications/PoP/roomtest/pop_bg.h +++ b/applications/PoP/roomtest/pop_bg.h @@ -58,6 +58,12 @@ void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, int8_t dir, uint8_t frame, uint8_t action); +/* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка, + * без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата + * (до ×8/7), как у kid_fp_obj_x(). */ +void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h, + int8_t dir); + /* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ * (draw_other_overlay/floor_overlay/полоса потолка): он выходит за * прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице diff --git a/applications/PoP/roomtest/pop_gdraw.c b/applications/PoP/roomtest/pop_gdraw.c new file mode 100644 index 0000000..b7b295c --- /dev/null +++ b/applications/PoP/roomtest/pop_gdraw.c @@ -0,0 +1,102 @@ +/* + * pop_gdraw.c — ОТРИСОВКА стража. Резидент окна W3 (--w3 pop_gdraw.c). + * + * Почему отдельным файлом, а не внутри pop_guard.c: --w3 забирает МОДУЛЬ + * целиком (код+rodata в 0xC000), а состояние стража и его логика обязаны + * остаться в W1/W2 — из банка (guards.c) резидент W3 недостижим ни прямо, + * ни транзитивно (docs/layout_plan_v2.md §2 R2). Значит граница проходит + * ровно между «что рисует» (зовёт только главный цикл → резидент) и «что + * знает» (зовёт и банк → W1/W2). + * + * Писучие статики резидента (--dataseg не трогаем) остаются в обычном + * _DATA, то есть в W2 — сюда переезжает КОД, не данные. + */ +#include +#include +#include +#include "pop_guard.h" +#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */ +#include "kid_data.h" + +/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */ +#define GUARD_PAGES 5 +static atlas_t gp[GUARD_PAGES]; +static uint8_t g_ok; + +/* Прямоугольник последней отрисовки ПО СТРАНИЦЕ дабл-буфера (как у Kid: + * heal обязан стирать спрайт именно той страницы, в которую рисуем). */ +static int gx_l[2], gy_l[2]; +static uint16_t gw_l[2], gh_l[2]; +static uint8_t gvalid[2]; + +int pop_guard_load(void) +{ + static const char *const names[GUARD_PAGES] = { + "GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl", + "GUARD\\g3.atl", "GUARD\\g4.atl" + }; + uint8_t i; + for (i = 0; i < GUARD_PAGES; i++) + if (atlas_load(&gp[i], names[i]) != 0) return -1; + g_ok = 1; + return 0; +} + +void pop_guard_free(void) +{ + uint8_t i; + if (!g_ok) return; + for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]); + g_ok = 0; +} + +void pop_guard_heal(void) +{ + uint8_t p = gfx_get_draw_page() & 1; + if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]); + gvalid[p] = 0; +} + +void pop_guard_draw(void) +{ + int obj_x, obj_y, top, fwd, fp_x; + uint8_t page, idx, p, flip; + const uint8_t *img; + uint16_t w, h; + + if (!g_ok || Guard.charid == 0 || pop_gframe.image == 255) return; + /* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */ + fwd = (int)Guard.x + ((Guard.direction < 0) ? -pop_gframe.dx : pop_gframe.dx); + obj_x = (fwd << 1) - 116; + obj_y = (int)Guard.y + pop_gframe.dy; + if ((int8_t)(pop_gframe.flags ^ Guard.direction) >= 0) obj_x++; + fp_x = obj_x; /* ЛОГИЧЕСКАЯ X (до ×8/7) — футпринт */ + obj_x = obj_x * 8 / 7; /* calc_screen_x_coord */ + + page = (uint8_t)(pop_gframe.image >> 3); + idx = (uint8_t)(pop_gframe.image & 7); + if (page >= GUARD_PAGES) return; + img = (const uint8_t *)atlas_image(&gp[page], idx); + gfx_w0_map(gp[page].page); + w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); + h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); + if (w && h) { + int bx = obj_x; + 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); + 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; + } + gfx_w0_unmap(); + /* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись + * midtable, а foretable рисуется после всех midtable-записей, поэтому + * колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid + * (seg008:690 draw_tile_fore). Без этого страж виден сквозь передний + * столб. */ + if (w && h) + pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction); +} diff --git a/applications/PoP/roomtest/pop_guard.c b/applications/PoP/roomtest/pop_guard.c index d2bc06a..c73ea9c 100644 --- a/applications/PoP/roomtest/pop_guard.c +++ b/applications/PoP/roomtest/pop_guard.c @@ -1,21 +1,25 @@ /* - * pop_guard.c — состояние стража (W1/W2). См. pop_guard.h. - * Логика — в банке (guards.c), отрисовка — из главного цикла. + * pop_guard.c — СОСТОЯНИЕ и ЛОГИКА стража (W1/W2). См. pop_guard.h. + * + * Здесь только то, что должно оставаться достижимым из банка (guards.c): + * поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы. + * ОТРИСОВКА — в pop_gdraw.c, резиденте W3 (её зовёт лишь главный цикл). */ -#include -#include -#include +#include #include "pop_guard.h" #include "pop_geom.h" #include "pop_level.h" -#include "pop_kid.h" /* play_seq/kid_set_seq пока общие (seqtbl один) */ -#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */ +#include "pop_kid.h" /* pop_kid_data_frame — таблицы в EMM-странице */ #include "kid_data.h" pop_char_t Guard; uint8_t guardhp_curr, guardhp_max; int8_t guardhp_delta; +/* Кадр стража (порт cur_frame для Char). Глобальный, а не статик: его + * заполняет логика (W1/W2), а читает отрисовка из резидента W3. */ +kframe pop_gframe; + uint8_t pop_guard_present(void) { return (uint8_t)(Guard.charid != 0 && Guard.alive < 0 && guardhp_curr != 0); @@ -45,51 +49,16 @@ void pop_guard_kill(void) Guard.alive = 0; } -/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */ -#define GUARD_PAGES 5 -static atlas_t gp[GUARD_PAGES]; -static uint8_t g_ok; - -/* Прямоугольник последней отрисовки ПО СТРАНИЦЕ дабл-буфера (как у Kid: - * heal обязан стирать спрайт именно той страницы, в которую рисуем). */ -static int gx_l[2], gy_l[2]; -static uint16_t gw_l[2], gh_l[2]; -static uint8_t gvalid[2]; - -/* Кадр стража (порт cur_frame для Char): таблица у стражей СВОЯ - * (frame_tbl_guard), и индексируется как frame + add_frame − 149, где +/* load_frame для стража (seg006:0293 ветка charid_2_guard): таблица у + * стражей СВОЯ (frame_tbl_guard), индекс = frame + add_frame − 149, где * add_frame = 70 для кадров 102..106 (seg006:535). */ -static kframe gframe; - -int pop_guard_load(void) -{ - static const char *const names[GUARD_PAGES] = { - "GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl", - "GUARD\\g3.atl", "GUARD\\g4.atl" - }; - uint8_t i; - for (i = 0; i < GUARD_PAGES; i++) - if (atlas_load(&gp[i], names[i]) != 0) return -1; - g_ok = 1; - return 0; -} - -void pop_guard_free(void) -{ - uint8_t i; - if (!g_ok) return; - for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]); - g_ok = 0; -} - -/* load_frame для стража (seg006:0293 ветка charid_2_guard). */ -static void guard_load_frame(void) +void pop_guard_load_frame(void) { int16_t idx = (int16_t)Guard.frame; if (idx >= 102 && idx < 107) idx += 70; /* add_frame */ idx -= 149; - if (idx < 0 || idx >= KID_NGFRAMES) { gframe.image = 255; return; } - pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&gframe); + if (idx < 0 || idx >= KID_NGFRAMES) { pop_gframe.image = 255; return; } + pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&pop_gframe); } void pop_guard_enter(uint8_t room) @@ -118,48 +87,5 @@ void pop_guard_enter(uint8_t room) Guard.frame = 166; guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */ guardhp_delta = 0; - guard_load_frame(); -} - -void pop_guard_heal(void) -{ - uint8_t p = gfx_get_draw_page() & 1; - if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]); - gvalid[p] = 0; -} - -void pop_guard_draw(void) -{ - int obj_x, obj_y, top, fwd; - uint8_t page, idx, p, flip; - const uint8_t *img; - uint16_t w, h; - - if (!g_ok || Guard.charid == 0 || gframe.image == 255) return; - /* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */ - fwd = (int)Guard.x + ((Guard.direction < 0) ? -gframe.dx : gframe.dx); - obj_x = (fwd << 1) - 116; - obj_y = (int)Guard.y + gframe.dy; - if ((int8_t)(gframe.flags ^ Guard.direction) >= 0) obj_x++; - obj_x = obj_x * 8 / 7; /* calc_screen_x_coord */ - - page = (uint8_t)(gframe.image >> 3); - idx = (uint8_t)(gframe.image & 7); - if (page >= GUARD_PAGES) return; - img = (const uint8_t *)atlas_image(&gp[page], idx); - gfx_w0_map(gp[page].page); - w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); - h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); - if (w && h) { - int bx = obj_x; - 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); - 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; - } - gfx_w0_unmap(); + pop_guard_load_frame(); } diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h index 58193d1..d11e192 100644 --- a/applications/PoP/roomtest/pop_guard.h +++ b/applications/PoP/roomtest/pop_guard.h @@ -7,9 +7,9 @@ * линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк — * только код; * - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c; - * - ОТРИСОВКА стража — из W1/W2 (рядом с kid_draw): из банка примитивы - * libbgi звать можно, но резидент W3 (pop_bg) — нельзя, поэтому слой - * фона/окклюзии за стража делает главный цикл. + * - ОТРИСОВКА стража — в pop_gdraw.c, РЕЗИДЕНТЕ W3 (--w3): её зовёт + * только главный цикл, а из банка резидент недостижим. Так код + * отрисовки не занимает дефицитные W1/W2 (куча). * * Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006 * ложился один в один. @@ -18,6 +18,7 @@ #define POP_GUARD_H #include +#include "kid_data.h" /* kframe */ /* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */ #define CHARID_0_KID 0 @@ -45,6 +46,11 @@ extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты extern uint8_t guardhp_curr, guardhp_max; extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */ +/* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2, + * читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */ +extern kframe pop_gframe; +void pop_guard_load_frame(void); + /* Есть ли живой страж в отрисованной комнате (для главного цикла). */ uint8_t pop_guard_present(void); @@ -58,9 +64,8 @@ void pop_guard_free(void); void pop_guard_enter(uint8_t room); /* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE, - * как kid_draw) и стереть прошлый (heal). Звать из главного цикла рядом - * с kid_draw: рисовать из БАНКА нельзя только резидентом W3, а тут мы в - * W1/W2. */ + * как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c + * (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */ void pop_guard_heal(void); void pop_guard_draw(void); diff --git a/applications/PoP/toolchain/pop_pack_guard.py b/applications/PoP/toolchain/pop_pack_guard.py index 1624c2e..1d03c29 100644 --- a/applications/PoP/toolchain/pop_pack_guard.py +++ b/applications/PoP/toolchain/pop_pack_guard.py @@ -28,12 +28,20 @@ PAL_BASE = 0x90 # слоты палитры Sprinter под страж COLOR = 2 # level.guards_color обоих стражей уровня 1 SHIFT = 3 # g[id>>3], idx id&7 (спрайты крупные) MASK = (1 << SHIFT) - 1 -MAX_ID = 34 # res751..res784 +MAX_ID = 33 # атлас-индексы 0..33 = res751..res784 def load_guard(id): - """id -> res(750+id).png -> (w, h, 8bpp COLUMN-MAJOR); 0 -> 0xFF (прозр.).""" - path = os.path.join(GUARD_DIR, f"res{750 + id}.png") + """Атлас-индекс id (== frame.image) -> res(751+id).png -> (w, h, 8bpp + COLUMN-MAJOR); 0 -> 0xFF (прозрачность). + + Off-by-one как у Kid (см. pop_pack_kid.load_kid): load_chtab_from_file + зовётся с ресурсом 750, а images[i-1] = res(750+i), то есть images[0] = + res751. Рисование берёт get_image(chtab, obj_id+1 − 1) = images[image], + значит frame.image=N -> res(751+N). С res(750+N) страж стоял в позе + предыдущего кадра (замечено на frame_166_stand_inactive: image 17 + рисовался как res767 — выпад — вместо res768). """ + path = os.path.join(GUARD_DIR, f"res{751 + id}.png") if not os.path.exists(path): return None im = Image.open(path) @@ -55,7 +63,7 @@ def main(): os.makedirs(OUT_DIR, exist_ok=True) pages = {} n = 0 - for id in range(1, MAX_ID + 1): + for id in range(0, MAX_ID + 1): g = load_guard(id) if g is None: continue