From 723da3c5c1b3929309335bf2e24d11e0959fa6bc Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 29 Jul 2026 11:34:07 +0300 Subject: [PATCH] =?UTF-8?q?PoP=20roomtest:=20=D0=BF=D0=B8=D0=BA=D0=B8=20?= =?UTF-8?q?=E2=80=94=20=D1=80=D0=B5=D0=B4=D1=80=D0=BE=D0=B9=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=B6=D0=B4=D1=8B=D0=B9=20=D0=BA=D0=B0=D0=B4=D1=80,=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D1=8B=D0=B9=20draw=5Ftile;=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=B0=D0=BC=D1=8F=20=D0=B1=D0=B5=D0=B7=20heal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Выдвинутая пика живёт ТОЛЬКО в видео-ОЗУ (банк SPRITE, в запечённый фон не входит), а kid_heal каждый кадр возвращает под спрайтом Кида печёный фон — то есть стирает попавшие под него части остриёв. Редрой «только при смене видимой сигнатуры» их не возвращал: hold держится 15 кадров (modif 0x8F..0x81) с неизменным кадром 5. Итог по дампу VRAM (комната 14, Kid на (2,7), modif 0x8E): у спрайта 132 стёрто третье остриё (x 245..247), у 138 — прямое целиком (x 258..260) и низ наклонного, страницы дабл-буфера расходились на 8 пикселей. SDLPoP: animate_spike (seg007:0353) зовёт redraw_21h БЕЗУСЛОВНО, вне всяких if — редрой каждый кадр, пока trob жив. pop_spike_redraw переписан на честный redraw_tile_height (seg007:0218): heal + ПОЛНЫЙ draw_tile своего тайла и правого соседа. Рисовать только два спрайта остриёв нельзя — теряется порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и fore соседа ложатся поверх), и остриё лезло на колонну (2,9). draw_tile заодно сам восстанавливает вклад соседних пик (ветка lcode==2), так что две пики подряд больше не гасят друг друга. Пламя факела: heal убран. Оригинал рисует его blitters_0_no_transp (seg008 draw_tile_anim_right), все 9 кадров лежат на общем канвасе 16x18 и бокс центрирован в ячейке — непрозрачный блит сам полностью накрывает предыдущий кадр. Кадры пакуются с opaque=True (POT_FLAME_IDS). Co-Authored-By: Claude Opus 5 --- applications/PoP/poc/res/bg/pop_pot.atl | Bin 4504 -> 4504 bytes applications/PoP/roomtest/pop_bg.c | 38 +++++++++---------- applications/PoP/roomtest/pop_trob.c | 44 +++++++++++++--------- applications/PoP/roomtest/roomtest.c | 6 +-- applications/PoP/toolchain/pop_pack_bg.py | 19 +++++++--- 5 files changed, 60 insertions(+), 47 deletions(-) diff --git a/applications/PoP/poc/res/bg/pop_pot.atl b/applications/PoP/poc/res/bg/pop_pot.atl index 8def38a0af968391376a491f31ba7f996bd7f706..e6c829eeaed1664e4484eb2b52f7edac2e60c037 100644 GIT binary patch literal 4504 zcmdT^JB|}U5FNuB;aR|DBf zBlhgB*sF!ut`z(J{IUek*Lz}r-Y?IA-#sXouxCrbpO%7GtN8-)b+jL%{Sxi>Xy*^5 z?pd^NqlH4N;-YxLXCo&4-PpipfK#P7RBk&AMoKY&jeb)De4NAyuvVC>HI_iC2RogN z9i+6O177E9@}_at$TXM>amFlw2rV?&DeuTDW=N$td4_{n5rnQ_8gsuQ6r9_kflBkf z4zCqE@{^iPX=M5MifREUWGCqB^@Xm)x7ai;**9WiJ~=RsRmZS^G%|Uw*#J+yE2WVl z#BLaxcO0lPk~R&GaoWgn`(0i=<)w@y}=>n33pynW7Ln#=x z=!mT!imo-lF!T6F@2u`Lme<4a;q}-A%)fvt>9gkY{ku{cnF=cqC~(wJ#)E(=K!=7N zJ3c?a^YgP(gq&CW9{X`)nyGeZq}VBwzn_#wrh|cPqN%oo*2)2LE#`pAQ?V|0cVcMr z_$cIn-;G{Eqm_nMAHIjYX5J2xAAeWS(!(kAUiTua+`k&;<_jXI16>J3I1OyDZ8g{I6#t zi@%yUuOSnims2NZ{`C6)Qp37mHaUGD+L{yc*QEs3d{SztA@WJlp@!0*B=s7en4i{8 zj%-pE=Xi%rz1_vtk`tOfl)Q%O+Lj8dctTUv^wU(ks^JBj?XJyUpoY4UmVgwsmc>#g zCi;}k@2BUI3SsDQ24T~HXkXh~uFPTE5R0a2nXI26 oMXpp0Oq2VD6o@ORTE#+cYRHAEHq)f8K|{)n6oV8EPk!h80`S87B>(^b literal 4504 zcmdT`v5pf#4BdkZ;`V@qt0=F$f&v;Un%wRt3v8m3_y{B_gb?3C&o5B&8GHbrK{xo> zvop45Hvu7#7<6Yp+t2pAn?$_6SwDSL{rz)wQdKAP^Y~x?1$T8Kb?yGx)A&Q zj@Z+?Vz17`4z<{K=a)75e7Ps~$Nl;o`0ayw346K}{BbFGwOX7ZzK-^Nw4bB>7VYAp z)IE##O|(#GRb5np&$XEJ``QMs3!Ey&p>oSG7%9~O-RLI~;Nzs80BeQ0T4M>MdUU6g zv4fO0?SR+0n!I_OH8KQqA=H717WY%wy)OhJrI48mKhy z>+o82M}AhbIgM;TzM@(H3fT$zdSjt0@f4fJB}XDQ=92@{STV{1(#Yh!W(A&mS4tz* z5W8V$-my?+B5erBAL4tJT|J<_1gBL)!D>Ga9K(7cwD(x?)w@y}=>n33pzcAwhEh;c zbi~$|qH7H>%sl?dJF7cQ<@IoUcs(`&^CzH6`mDM9_^y;jrosw@7C34s<07C6z|hcR z*Ut~|{rOp`hMZUZJ@(_KG*fhFq`Ffke?KXW41<9s(NtSPYvlmB7IQ%5saTi0J25nQ zd=zp(@*d~*G~C{C3THYIC*F~2_sbd?&S9YM!<7iAJ3>I5+~o*9l4eZ~w>$oQqqbp9 zJdVr$@;l-y=O%K6c`TY_MHoA%CG-6XJ_3sGUM*$NK^Hj8pH-Fj@9e;5?Xs%l{~;Cs ze{rKGFK5mx$VBJG+=-b#z3#u%upO68&KQWc<%IlpDS<7YlqxkuJ}FviDEuU;*YL>v zw03f2le#*`JM7xcHm;VO(2b$wbyU~)RM^B5x~8RnU9)XEUa;G2`|JfGw4G1_Qbet* zrA$oiCnk37mgfkcc-*twnC^KFFVQxerVs4G;nlDhU$5Bk5$&vP|yPDYS zq8y=*F6}6*Vcnjl$++wV+(oY1Q*ZJX)mi?2dM>FDN{2HD8v>$Z?P$3&hb redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ + * redraw ДВУХ тайлов — своего и правого соседа (остриё рисуется в его + * ячейке). Именно ПОЛНЫЙ: рисовать только два спрайта остриёв нельзя — + * порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и + * fore соседа ложатся ПОВЕРХ) тогда теряется, и остриё лезет на колонну + * справа. Заодно draw_tile сам восстанавливает вклад соседних пик: + * в ячейке col — правое остриё пики col-1 (ветка lcode==2). + * Шипы выдвигаются выше линии пола (dmy-7), поэтому heal с запасом вверх + * (63*row+34 .. +74); ширина 64 = обе ячейки. */ heal_off(x, 63 * row + 34, 64, 40); gfx_set_bank(GFX_BANK_SPRITE); - if (sf > 0 && sf < 10) { - if (SPIKES_FRAM_LEFT[sf]) /* остриё в своей ячейке */ - env_b(SPIKES_FRAM_LEFT[sf], x, dmy - 2); - if (col + 1 < 10 && SPIKES_FRAM_RIGHT[sf]) /* остриё в ячейке соседа */ - env_b(SPIKES_FRAM_RIGHT[sf], COL_XH[col + 1] * 8, dmy - 7); - } + draw_tile(row, col); + if (col + 1 < 10) + draw_tile(row, col + 1); gfx_set_bank(GFX_BANK_NORMAL); } @@ -723,7 +716,10 @@ void pop_torch_draw(int row, int col, uint8_t modif) int x = (col + 1 < 10 ? COL_XH[col + 1] : COL_XH[col] + 4) * 8 + 8; int yb = 63 * row + 62 - 40; if (modif >= 9) return; - heal_off(x - 1, yb - 18, 18, 19); + /* heal НЕ нужен: оригинал рисует пламя blitters_0_no_transp (seg008 + * draw_tile_anim_right), и все 9 кадров лежат на общем канвасе 16x18 — + * непрозрачный блит сам полностью накрывает предыдущий кадр. Кадры + * упакованы с opaque=True (pop_pack_bg POT_FLAME_IDS). */ gfx_set_bank(GFX_BANK_SPRITE); pot_b((uint8_t)(modif + 1), x, yb); gfx_set_bank(GFX_BANK_NORMAL); diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index 685f1c9..5cea81f 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -389,26 +389,34 @@ void pop_process_trobs(uint8_t cur_room) if (code == TILE_POTION) pop_potion_draw(trow, tcol, mod[tp]); else pop_torch_draw(trow, tcol, mod[tp]); } - if (room == cur_room && - (code == TILE_SPIKE || code == TILE_OPENER || code == TILE_CLOSER)) { - /* Редрой ТОЛЬКО при изменении видимого состояния (redraw_11h из - * SDLPoP). spike: draw-сигнатура = modifier; button: = «нажата» - * (таймер связи >1). Пока Кид держит кнопку — сигнатура стабильна - * → редроя нет. При изменении рисуем ТЕКУЩУЮ страницу сразу + - * =1 (вторая страница дабл-буфера следующим кадром через - * rest-цикл). tp/10,tp%10 считаем только в момент редроя (редко), - * а не каждый кадр. */ - /* Сигнатура = ВИДИМЫЙ кадр, а не сырой modifier. Для пики кадр = - * (mod&0x80)?5:mod (как в draw_tile): весь ретракт-hold 0x80..0x8F - * (16 значений modifier) рисуется одним кадром 5 → сырой modifier - * дал бы 16 ложных редроёв «выдвинутой» неподвижной пики. */ - uint8_t sig = (code == TILE_SPIKE) - ? (uint8_t)((mod[tp] & 0x80) ? 5 : mod[tp]) - : (uint8_t)(get_doorlink_timer(mod[tp]) > 1); + if (room == cur_room && code == TILE_SPIKE) { + /* ПИКИ — редрой КАЖДЫЙ кадр, пока trob жив: animate_spike + * (seg007:0353) зовёт redraw_21h БЕЗУСЛОВНО, а не по смене кадра. + * Это не «лишняя работа оригинала»: выдвинутая пика живёт только в + * видео-ОЗУ (банк SPRITE, в фон не запечена), а kid_heal каждый кадр + * возвращает под спрайтом Кида ПЕЧЁНЫЙ фон — то есть стирает те + * части остриёв, что попали под его прямоугольник. При редрое + * по смене кадра пика держится 15 кадров (modif 0x8F..0x81) с + * неизменной сигнатурой, и стёртое уже не возвращается: рядом с + * Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной + * странице дабл-буфера из двух). */ + uint8_t trow = tp / 10, tcol = tp % 10; + pop_spike_redraw(trow, tcol); + /* Последний кадр (пика убралась, trob умирает) — на ВТОРУЮ страницу + * его уже никто не положит: ставим отложенный редрой. */ + if (type < 0) { spike_rest[tp] = 1; rest_pending++; } + } + if (room == cur_room && (code == TILE_OPENER || code == TILE_CLOSER)) { + /* КНОПКА — редрой только при смене видимого состояния (нажата = + * таймер связи >1): пока Кид её держит, картинка стабильна. + * Рисуем ТЕКУЩУЮ страницу сразу + =1 (вторая страница + * дабл-буфера следующим кадром через rest-цикл). tp/10,tp%10 + * считаем только в момент редроя (редко), а не каждый кадр. */ + uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1); if (sig != trob_drawn[tp]) { uint8_t trow = tp / 10, tcol = tp % 10; - if (code == TILE_SPIKE) { pop_spike_redraw(trow, tcol); spike_rest[tp] = 1; rest_pending++; } - else { pop_button_redraw(trow, tcol); button_rest[tp] = 1; rest_pending++; } + pop_button_redraw(trow, tcol); + button_rest[tp] = 1; rest_pending++; trob_drawn[tp] = sig; } } diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index fc7cccc..e547d2e 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -26,9 +26,9 @@ __sfr __at (0xFE) io_border; /* Тест-старт для отладки пик (Фаза S): комната 6 = тестовая (пики в col3/col4 * row2, шахта col3). Kid на полу row0 col1, идёт вправо → падает в шахту → * пики. TODO: вернуть на pop_level_start_room (комната 1) после отладки. */ -#define START_ROOM 20 /* ОТЛАДКА: кнопка-closer (1,4) — отрисовка нажатой */ -#define START_COL 8 -#define START_ROW 1 +#define START_ROOM 14 /* ОТЛАДКА: пики (2,7)/(2,8) — редрой каждый кадр */ +#define START_COL 9 +#define START_ROW 2 #define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */ #define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */ diff --git a/applications/PoP/toolchain/pop_pack_bg.py b/applications/PoP/toolchain/pop_pack_bg.py index 3454751..adde653 100644 --- a/applications/PoP/toolchain/pop_pack_bg.py +++ b/applications/PoP/toolchain/pop_pack_bg.py @@ -194,10 +194,11 @@ def load_indexed(chtab, id, opaque=False): return im.width, im.height, out -def load_potion(id, mono=None): +def load_potion(id, mono=None, opaque=False): """chtab_1: res(150+id).png из data/PRINCE. mono=None — обычный спрайт (палитра chtab_1 -> слоты POT_PAL_BASE+i); mono=цвет — силуэт этим цветом - (порт blitters_40h_mono).""" + (порт blitters_40h_mono); opaque=True — пиксель 0 остаётся ЦВЕТОМ + (порт blitters_0_no_transp, см. POT_FLAME_IDS).""" path = os.path.join(os.path.dirname(R.VDUNGEON_DIR), POT_DIR_NAME, f"res{150 + id}.png") if not os.path.exists(path): @@ -206,10 +207,12 @@ def load_potion(id, mono=None): if im.mode != "P": die(f"res{150+id}: mode {im.mode}, ожидался P") src = im.tobytes() - if mono is None: - out = bytes((0xFF if v == 0 else POT_PAL_BASE + v) for v in src) - else: + if mono is not None: out = bytes((0xFF if v == 0 else mono) for v in src) + elif opaque: + out = bytes((POT_PAL_BASE + v) for v in src) + else: + out = bytes((0xFF if v == 0 else POT_PAL_BASE + v) for v in src) return im.width, im.height, out @@ -399,6 +402,12 @@ def main(): pot_entries[id] = load_potion(id, POT_BUBBLE_COLOR) elif id == POT_MASK_ID: pot_entries[id] = load_potion(id, POT_MASK_COLOR) + elif id in POT_FLAME_IDS: + # Пламя оригинал рисует blitters_0_no_transp (seg008 + # draw_tile_anim_right) — непрозрачным боксом поверх всего, что + # сзади. Все 9 кадров лежат на одном канвасе 16x18, поэтому + # непрозрачный блит сам стирает предыдущий кадр: heal не нужен. + pot_entries[id] = load_potion(id, opaque=True) else: pot_entries[id] = load_potion(id) cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_pot.atl"), pot_entries)