From af5f0a4638b11b46e0939ca90ab26e1e881a917a Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 29 Jul 2026 23:14:21 +0300 Subject: [PATCH] =?UTF-8?q?PoP=20roomtest:=20=D0=BE=D1=82=D1=80=D0=B8?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=D0=BA=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=B6?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D1=80=D0=B5=D0=B7=D0=B8=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=20W3=20+=20fore-=D0=BE=D0=BA=D0=BA=D0=BB=D1=8E=D0=B7?= =?UTF-8?q?=D0=B8=D1=8F=20+=20off-by-one=20=D1=81=D0=BF=D1=80=D0=B0=D0=B9?= =?UTF-8?q?=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Разгрузка W1/W2 перед ИИ стражей (вариант 2 из двух обсуждённых). 1. pop_guard.c разделён по окнам: состояние/логика (Guard, HP, enter, kill, load_frame) остаются в W1/W2 — их обязан видеть банк guards.c; ОТРИСОВКА уехала в новый pop_gdraw.c, собираемый как --w3 (резидент). Правило границы: резидент = только то, что рисует и зовётся исключительно из главного цикла. Кадр стража стал глобальным (pop_gframe): заполняет логика, читает резидент. 2. Страж не окклюдировался передними гранями тайлов — рисовался поверх столба. В оригинале любой Char это запись midtable, а foretable рисуется после всех midtable (draw_tile_fore, seg008:690), т.е. столб перекрывает всех одинаково. Футпринт персонажа выделен из pop_fore_over_kid в char_footprint(), поверх него добавлен pop_fore_over_char() — слой fore + полоса потолка, без оверлеев поз виса/полёта/подъёма (у стража их нет; появятся — портируем redraw_at_char2 общим кодом, а не догадками). 3. Упаковщик стража: тот же off-by-one, что уже ловили у Kid. load_chtab_from_file(id_chtab_5_guard, 750) даёт images[0] = res751, а рисование индексирует images[frame.image] — значит image=N это res(751+N), а не res(750+N). Из-за сдвига frame_166_stand_inactive рисовался как res767 (выпад) вместо res768 (стойка). Проверено в MAME: страж в комнатах 3 и 21 стоит в правильной позе; окклюзия подтверждена патчем Guard.x в живой сессии — при заходе за столб спрайт корректно срезается его передней гранью. Память: _CODE 26 149 -> 25 703, куча W2 805 -> 1245 Б, резидент W3 11 656 -> 12 819 (свободно 3565 Б), банк 1 236/16384 Б. Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/layout_plan_v2.md | 11 +- applications/PoP/poc/res/guard/g0.atl | Bin 8307 -> 9711 bytes applications/PoP/poc/res/guard/g1.atl | Bin 10536 -> 10732 bytes applications/PoP/poc/res/guard/g2.atl | Bin 10639 -> 10079 bytes applications/PoP/poc/res/guard/g3.atl | Bin 5806 -> 5232 bytes applications/PoP/poc/res/guard/g4.atl | Bin 1605 -> 1131 bytes applications/PoP/roomtest/Makefile | 15 +-- applications/PoP/roomtest/pop_bg.c | 54 +++++++++- applications/PoP/roomtest/pop_bg.h | 6 ++ applications/PoP/roomtest/pop_gdraw.c | 102 ++++++++++++++++++ applications/PoP/roomtest/pop_guard.c | 108 +++---------------- applications/PoP/roomtest/pop_guard.h | 17 +-- applications/PoP/toolchain/pop_pack_guard.py | 16 ++- 13 files changed, 217 insertions(+), 112 deletions(-) create mode 100644 applications/PoP/roomtest/pop_gdraw.c 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 87ab285adfcf268c79c18a8fc835fbbbf073e6d2..4041d2cfd38b884f5599070529bd6f64d1f5aa9f 100644 GIT binary patch delta 637 zcmZ9IO-{l<7={tSp%Adi*cb{)fr{x7T$t#_l?y$AZe42Jx#0-XzP9N^(!|6AhlzOVh+{oEg(gr{4j^Nt^kZ=b!AUkD1gscay#YB;uB zeANTk8 z;n=pC%drrp{}Rp$2EH;m;q*3&p{A(}sZeR0qD|jtT%szjxXa`>yAYK?lnM{tYDtY6 z4B(j0yrBJuLW*RMsYX2v{EJ}`yyeBblhZH~1&9RX81g@PAQ z9ORUs^)ZG%A=q_vyb?=#g6BtKqGm==b&OHg=FpX2;!8>KJwHemhfX{uDsu4+GXsAq z12(JS!L;Z=lkw8b(ZLLcu^6`FC9J1yRMUhG!jmzK3oVCU-oR;c6(>mtH*Q&F-&Af7 AF#rGn delta 630 zcmY+Czityj5XSFv=YK@L+vIG=Mwab(?f6n*8fZxT#33CDlD zL&Ej9_p7)N0x$Xj`rDx?Tw;d6_?&1>TSDS{u?hl&oS$_zBlY-_#f$GEVTdt diff --git a/applications/PoP/poc/res/guard/g2.atl b/applications/PoP/poc/res/guard/g2.atl index cd1d58d7b41079b8eecc3e4debbb1b0b39953801..5bf728c06eceafb57a9b66936b93281ef18523d7 100644 GIT binary patch delta 544 zcmZ9G%}T>S6onI;KQR`Y)+GHwQ=9&RkOvU03%BjU2N2vAx^l1J#$8R06=F&sK`MQL z?gd|=-3xAAj(3t2f(K^qopZi>naTBf@I1O|i=rTyYPHAf(_YWz^{#NzvC$so&mG6Cevw7mAQ?pqZ zW@o79P%^|a&=w0wF~q+C(49atzKO(_aVDT7qO%o6`7lhOm<*Id^i4_6dSG&eOcjnP mW0~|~h#UQwqxdQ+`0aFXp*O?;zU!jjb#dNR5jjqU zJ!4A$(VCH*saw)l2%h37u#;~%Fm&|P1+-O%&Z_F8q0Naw68%{nOIl?_d8L^c7Bjfg z%CM?QVp9y_f6AmEh7sY{sg5Vm*YbF&rjAG!f=waOl;EBLAd12$ppslxMx-t%6`Y6i ziNC3ztC~DTpb$VITBIdYLBMw{iV?9O41=K6qFk%n<+Pv_PGV+;d)B$XiJ4eW=g^Pq z_>7gXnNG_(9e1!2cMb4j{4`AvXa$ik!kQh^rG!_zjKgdSPj(62tl{egwC%jFPhiWb z;G>Ymj#K(&G59BQds@(OtWCqJ7+9{<={R^$-La_Ci9E&S?`-2TH;Lg?4h=U&-&w=H Kk+|!rxAr%s?E2UM diff --git a/applications/PoP/poc/res/guard/g3.atl b/applications/PoP/poc/res/guard/g3.atl index c7eeae7b60b018846efc826c93b42a488f425634..3f8bc1579bb22628b0fb440d8b6768ded9a7dd0f 100644 GIT binary patch delta 455 zcmZvWu}T9$5Qg_m;0}Tbn%p%O3R*-%2(5t*V`#=&PD7h)9GbNS0UOkHRA}Z&7AIBEWxW zU`9iPL@=MT6hcTac3o`S)*2|#N|O=6xBzF;eR$T`$JTqJwAxTW-}fnOFm(WWNnzV% u%|^9%O?4qwiYT;e;|UF8wtC delta 581 zcmZuuyH3ME5cDPv=HWOvF(yC|5lN&dh?0UX4K@4$G&D#Q2!4P+z!{8$Y+2$55E3mC zzW^N#Qa-`7G{^3p4JjDOH?y;|dpbEEKJMO}9drgnmi1zXec=y>7?$>{f#;M-ni?|+?><0wHC!Kcm~ z5OaYqdks0w+wpCzi|Do12P3|_XXt_Qh!~{?+Lg^?Qznw<2^shsk#qs2lB5X1gv{cy zuURrlW>ys;M3q*+uF8yG*lCJuH#k)$tA1S(X61yziYNJn0G}q5rl}T^lFWoVk}08^ zMMmoho}2(D4cnV_mDp{(`7Rli`*rOcH4Tr+@U-gH_!fTrd$sGx0~xHhYNhHfvC`Lv UR-gZI3p=!p5%uJI9?_Nm1AlxOWB>pF diff --git a/applications/PoP/poc/res/guard/g4.atl b/applications/PoP/poc/res/guard/g4.atl index 8f8cae06d86f3992fd318df97f779f228644527a..25e914abe507e4d1a1be84bf58c480066c1c11cc 100644 GIT binary patch delta 42 tcmX@g^O}P-IKa`6X`*2GL0?*!Gz8|81C8X3-Kb{?G<#O;!h)qq&l@^(RCl6k_SCqD5oyJ_oBclEMH@hnYKA z7NEv}Mk0DpCW+TYW6W-v5$q)270FT7bhc7=iWr4P4-f2HF|4RCn5J+y_Y`Ylha_67Fxa&vnIuFUkR!3&cLcl>#*p^e$MO%M8I^%o|;xMBQLF> 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