From d7d18aef771d1205d265a3fff32969b83107c31b Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 12 Aug 2026 12:13:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B5=D0=BB=D1=8C=D0=B5=20=D0=BC=D0=B5?= =?UTF-8?q?=D0=B4=D0=BB=D0=B5=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F=20(=D0=BF=D0=B5=D1=80=D0=BE)=20+?= =?UTF-8?q?=20=D1=86=D0=B2=D0=B5=D1=82=20=D0=BF=D1=83=D0=B7=D1=8B=D1=80?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=D0=B2=20=D0=BF=D0=BE=20=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D1=83=20=D0=B7=D0=B5=D0=BB=D1=8C=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L7-FEATHER: тип 3 (уровень 7, комната 1) больше не пустой TODO. - pop_map.c: pop_feather — счётчик кадров эффекта (POP_FEATHER_FRAMES = 225, ванильный порог do_timers seg003:0517; привязку к звуку взять неоткуда). fall_accel — ускорение 1 / потолок 4 (seg006:057C) вместо 3 / 33. proc_get_object case 3: взвести эффект + зелёная вспышка на 3 кадра. - pop_kid.c: опкод JMP_IF_FEATHER (0xF7) больше не пропускает адрес безусловно — под пером прыгает по нему, то есть seqtbl уводит падение и удар в ветки stepfloat/bumpfloat (плавные кадры, без урона). - roomtest.c: pop_flash_red -> pop_flash_color (жёлтый/красный/ЗЕЛЁНЫЙ); сброс pop_feather в pop_start_level (seg003:189). - Цвет пузырька по ТИПУ зелья (seg008:652), чего у нас не было вовсе: 3/4 зелёный, 5/6 СИНИЙ, остальные красный. Mono-блиттера с параметром цвета в libbgi нет, поэтому цвет запекается при упаковке: pop_pack_bg.py кладёт те же 7 кадров ещё дважды (id 30..36 зелёные, 40..46 синие), pop_potion_draw выбирает набор. Атлас 23 -> 37 спрайтов (+423 Б). - Синее зелье «−HP» приведено к оригиналу (seg006:1892): своей вспышки не ставит (красный кадр даёт общий flash_if_hurt — иначе экран красился дважды), а на уровне зелий забирает ПОЛОВИНУ запаса HP. Такое зелье стоит уже на пройденном уровне 2 (комната 13, тайл (1,3)), а также ур.8 комн.2 и весь ур.15 — до сих пор оно было красным. Тесты: phys_feather_fall_is_slow_and_harmless (обычное падение с двух рядов разгоняется и стоит HP, под пером скорость <= 4 и HP целое); tests-host 5/5 (1727 в [phys]), size-check OK. Расхождения с ванилью — в docs/impl_diff.md (перо ловит только Кида; синее зелье без своей вспышки). Co-Authored-By: Claude Opus 5 --- applications/PoP/docs/impl_diff.md | 38 +++++++++++ applications/PoP/poc/res/bg/pop_bg_atlas.h | 5 ++ applications/PoP/poc/res/bg/pop_pot.atl | Bin 4504 -> 5374 bytes applications/PoP/roomtest/BUGS_OPEN.md | 31 +++++++++ applications/PoP/roomtest/TASKS_OPEN.md | 10 ++- applications/PoP/roomtest/pop_kid.c | 15 ++++- applications/PoP/roomtest/pop_map.c | 62 ++++++++++++++---- applications/PoP/roomtest/pop_map.h | 13 +++- applications/PoP/roomtest/pop_room.c | 16 ++++- applications/PoP/roomtest/pop_tune.h | 16 +++++ applications/PoP/roomtest/roomtest.c | 12 ++-- applications/PoP/roomtest/tests-host/t_phys.c | 52 +++++++++++++++ applications/PoP/toolchain/pop_pack_bg.py | 21 ++++++ 13 files changed, 267 insertions(+), 24 deletions(-) diff --git a/applications/PoP/docs/impl_diff.md b/applications/PoP/docs/impl_diff.md index 8fd2f1d..1518b8c 100644 --- a/applications/PoP/docs/impl_diff.md +++ b/applications/PoP/docs/impl_diff.md @@ -171,3 +171,41 @@ for (short column = 9; column >= 0; --column) { направления: нажать её из комнаты 11 и войти в 24; и наоборот — войти в 24 поверху и наступить на неё, стоя в шве. Картинка кнопки обязана совпадать со статусом ворот в обоих случаях. + +--- + +## Перо (медленное падение) ловит ТОЛЬКО Кида + +**Оригинал** (`fall_accel`, seg006:057C): `is_feather_fall` — глобальный флаг, +и медленное падение достаётся ЛЮБОМУ персонажу, который окажется в `Char`, +пока эффект жив. То есть страж, сошедший с уступа в те же секунды, парит +вместе с Кидом, хотя зелье пил не он. SDLPoP считает это багом и чинит +опцией `fix_feather_fall_affects_guards`. + +**Мы** берём поведение С ФИКСОМ: `pop_feather` проверяется вместе с +`Char.charid == CHARID_0_KID` — и в `fall_accel` (`pop_map.c`), и в опкоде +`JMP_IF_FEATHER` интерпретатора seqtbl (`pop_kid.c`), чтобы физика и анимация +не разъехались. + +**Чем платим.** Сцена, где страж падает при живом пере, будет выглядеть иначе, +чем в DOS-оригинале (у нас он падает нормально, там — парит). На уровне 7, +единственном с этим зельем, такой сцены нет: зелье в комнате 1, стражи — в +других комнатах. + +**Что проверять при регрессе.** Уровень 7: выпить зелье в комнате 1, тут же +столкнуть стража в провал — он обязан падать БЫСТРО, а Кид рядом — медленно. + +--- + +## Синее зелье («−HP») не ставит свою вспышку + +**Оригинал** (`proc_get_object`, seg006:1892): ветка `case 5` только глушит +звуки, играет `sound_13_kid_hurt` и ставит `hitp_delta`. Экран краснеет не +здесь, а общим механизмом «Кид ранен» (`flash_if_hurt`, seg003:0AFC). + +**Мы** раньше ставили в этой ветке ещё и `pop_flash_*` (красную вспышку на 2 +кадра) — то есть красили экран дважды: своей вспышкой и кадром урона. +Приведено к оригиналу: ветка правит только `hitp_delta`, краснеет `pop_kid_hurt`. + +**Что проверять при регрессе.** Уровень 2, комната 13, зелье `(1,3)`: выпить — +HP убавляется на единицу, экран краснеет РОВНО один раз (без двойного строба). diff --git a/applications/PoP/poc/res/bg/pop_bg_atlas.h b/applications/PoP/poc/res/bg/pop_bg_atlas.h index e62944c..9e861ea 100644 --- a/applications/PoP/poc/res/bg/pop_bg_atlas.h +++ b/applications/PoP/poc/res/bg/pop_bg_atlas.h @@ -37,6 +37,11 @@ static const char *const pop_tile_pal[POP_TILESETS] = { "pop_tile.pal", "pal_til #endif /* POP_BG_ATLAS_NAMES */ #define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья, от набора не зависит */ #define POP_PAL_POT 0x40 +/* Пузырёк зелья: красный набор = id 16..22 (кадры оригинала), + зелёный (перо/переворот) и синий (вред/открыть) = те же кадры + под id 30..36 и 40..46 (draw_tile_anim, seg008:652). */ +#define POP_POT_BUBB_GREEN 30 +#define POP_POT_BUBB_BLUE 40 #define POP_BG_PAL "pop_bg.pal" #endif diff --git a/applications/PoP/poc/res/bg/pop_pot.atl b/applications/PoP/poc/res/bg/pop_pot.atl index e6c829eeaed1664e4484eb2b52f7edac2e60c037..e2e3695492cd1d1f819a3031de04c12e2e23779a 100644 GIT binary patch literal 5374 zcmdT^J8u&~5Wa{6Vh6AZ>4~TiLOe<$3hcYH53sFBl>7uJ5j{OWfP$VDQHP#}o-z{D z^i;>Zc6Mg>&JIE#u~9Ow@B3zUvi8&C$-O(p-#@2YMX?3H-?zs5Xm`)Cy|vBuc!%xF z^K9Q~pd2#C{Qr z|JiY|Up%BUWut$mJ~7Q=MY02M&u}r8i~_2`uM*J5qna2@OiXASb3*Z`4kyM*2xTXS z@g$yP7(9Kl_PDsBMGz(CndaYh<3h5*GAdn6%_h=K{AKPJ8rZu!-B}j*y(_aLBZ#wM z(>P` z=%o!(eR@_#km^Q1jnj5rJ2Q0Lkx_?A{C+Y!atsPQ3A%a+j1?2$`j`@?4qyvU?nI%h z<3}MTxSqyQ9-8|*cEynn#Ey4l1GKkhkCa7}O?^&P4F>Gl8R4y!P%Iw2vpq5}U-N*q>nw3P1<>$=f=hs@WNraRow zU&mI5Cm9xd_zvr8HkZ-VY3q z7;#6H@90=pbLzu<;E`rMbLz1V4N;Y|vT4ahhepG}=z3L6%~3}@yfe;&Jc<@23Y`YTMkQ3Z@+avjH0{A!FJT1k{Hm zvCx4*3Dh*y&Vir_8NpzU3b;y$K!c4k%E|~@=>VfB67K%aaUG7rT)9IPOgjQ*12$Mf z#_@gO~?oaYgE8hLIfIYlu=ej&`JjwMSlVKG+ZPA delta 209 zcmeyTIYXH>IKa_RVxnO9L diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index 3875f75..3f8c6de 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -89,8 +89,14 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS. ### L7-FEATHER. Зелье МЕДЛЕННОГО ПАДЕНИЯ (уровень 7, комната 1) -**Задача (пользователь, 2026-08-12): составить план — здесь он и есть; код -следующим заходом.** +> **Статус 2026-08-12: код написан, ждёт живой проверки в MAME.** Сделаны все +> шесть шагов ниже плюс синие пузырьки зелья «−HP» (тип 5) — они всплыли по +> ходу: пользователь напомнил, что такое зелье стоит уже на уровне 2 (комната +> 13, тайл `(1,3)`; ещё ур.8 комн.2 и весь ур.15), а цвет у него был красный. +> Хост-тесты: `phys_feather_fall_is_slow_and_harmless` (скорость ≤ 4 и HP +> целое против обычного падения с двух рядов). **Осталось**: проверить в MAME +> — ур.7 комн.1 (зелёные пузырьки, зелёная вспышка, медленный спуск в шахту) и +> ур.2 комн.13 (синие пузырьки, −1 HP, экран краснеет один раз). Зелье на `(2,8)` комнаты 1 уровня 7 (сверено по `res2007.bin`: modifier 3, то есть `potion_type = 3` — «slow fall»). Сейчас `pop_proc_get_object` diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index e855318..c479dc7 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -426,9 +426,18 @@ void play_seq(void) case 0xFE: /* FLIP */ Char.direction = (int8_t)~Char.direction; break; - case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */ - Char.curr_seq += 2; - break; + case 0xF7: /* JMP_IF_FEATHER (seg006:586): под зельем медленного + * падения — перейти по адресу (в seqtbl это ветки + * stepfloat/bumpfloat: «парящие» кадры падения и удара, + * без урона), иначе пропустить адрес и идти дальше. + * Эффект достаётся только Киду — как и сама физика пера + * (fall_accel, pop_map.c). */ + if (!pop_feather || Char.charid != CHARID_0_KID) { + Char.curr_seq += 2; + break; + } + /* иначе — ПРОВАЛ в JMP ниже: адрес лежит ровно там же */ + /* fallthrough */ case 0xFF: { /* JMP */ uint16_t a = (uint16_t)(SEQ(Char.curr_seq) | ((uint16_t)SEQ((uint16_t)(Char.curr_seq + 1)) << 8)); diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 6234863..bed36f1 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -118,6 +118,10 @@ #define SCREENSPACE_X 58 #define FALL_ACCEL 3 #define FALL_MAX 33 +/* Перо (seg006:057C, types.h:1435): ускорение и потолок скорости падения + * под зельем медленного падения. */ +#define FALL_ACCEL_FEATHER 1 +#define FALL_MAX_FEATHER 4 #define TILE_MIDX 7 #define FRAME_THIN 0x20 @@ -158,6 +162,11 @@ static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */ static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */ static uint8_t grab_timer; /* после зацепа (12): блокирует climb-up */ +/* is_feather_fall (seg000:15F8): кадров, пока действует зелье медленного + * падения. У оригинала это флаг + счётчик тиков со сроком по звуку; у нас + * просто обратный отсчёт кадров (POP_FEATHER_FRAMES, pop_tune.h). Публичный: + * его читают play_seq (опкод JMP_IF_FEATHER, pop_kid.c) и приложение. */ +uint8_t pop_feather; /* Состояние edge-триггера бампа — массивы флагов перекрытия по колонкам * (порт prev_coll_flags/curr_row_coll_flags, seg004); объявлены ниже, рядом * с check_collisions. Сброс при смене комнаты — coll_prev_row. */ @@ -823,8 +832,19 @@ static void do_fall(void) static void fall_accel(void) { if (Char.action == ACT_FREEFALL) { - int fy = (uint8_t)Char.fall_y + FALL_ACCEL; - if (fy > FALL_MAX) fy = FALL_MAX; + int fy; + /* Перо (seg006:057C). Оригинал ловит эффектом ЛЮБОГО персонажа в + * Char — зелье пьёт только Кид, но флаг глобальный, и страж, упавший + * в ту же минуту, тоже парит; SDLPoP чинит это опцией + * fix_feather_fall_affects_guards. Берём поведение С ФИКСОМ (только + * Кид): расхождение осознанное, запись в ../docs/impl_diff.md. */ + if (pop_feather && Char.charid == CHARID_0_KID) { + fy = (uint8_t)Char.fall_y + FALL_ACCEL_FEATHER; + if (fy > FALL_MAX_FEATHER) fy = FALL_MAX_FEATHER; + } else { + fy = (uint8_t)Char.fall_y + FALL_ACCEL; + if (fy > FALL_MAX) fy = FALL_MAX; + } Char.fall_y = (int8_t)fy; } } @@ -1157,7 +1177,7 @@ uint8_t pop_item_taken; /* 0 = нет; иначе tilepos+1 подн uint8_t pop_flash_time; /* кадров вспышки фона (flash_time) */ /* Цвет вспышки: 0 = ярко-жёлтая (меч), 1 = красная (зелье). В оригинале это * flash_color; нам хватает двух значений, которые реально встречаются. */ -uint8_t pop_flash_red; +uint8_t pop_flash_color; static int8_t pickup_obj_type; /* -1 = меч (порт pickup_obj_type) */ /* do_pickup (seg006:1671): стереть предмет с карты (тайл → пол) и запомнить @@ -1222,22 +1242,38 @@ void pop_proc_get_object(void) __banked case 1: /* здоровье: +1 HP, красная вспышка */ if (hitp_curr != hitp_max) { hitp_delta = 1; - pop_flash_red = 1; + pop_flash_color = POP_FLASH_RED; pop_flash_time = 2; } break; case 2: /* большая склянка: +1 к ПОТОЛКУ HP */ pop_kid_add_life(); - pop_flash_red = 1; + pop_flash_color = POP_FLASH_RED; pop_flash_time = 4; /* seg006:1878 — вспышка длиннее */ break; - case 5: /* «злое» зелье: −1 HP */ - hitp_delta = -1; - pop_flash_red = 1; - pop_flash_time = 2; + case 3: /* ПЕРО: медленное падение */ + /* feather_fall (seg000:15F8): взвести эффект + ЗЕЛЁНАЯ вспышка + * на 3 кадра (flash_color = 2). Оригинал здесь же глушит звуки + * и играет sound_39_low_weight — по нему же меряет срок; у нас + * срок задан кадрами (POP_FEATHER_FRAMES). */ + pop_feather = POP_FEATHER_FRAMES; + pop_flash_color = POP_FLASH_GREEN; + pop_flash_time = 3; break; - default: /* 3 перо / 4 переворот / 6 открыть — - * TODO вместе с их уровнями (7/9) */ + case 5: /* «злое» зелье (синее): −1 HP */ + /* seg006:1892. Своей вспышки у этой ветки НЕТ (в отличие от + * лечебных): красный кадр даёт общий механизм «Кид ранен» + * (flash_if_hurt, seg003:0AFC) — у нас pop_kid_hurt. Раньше мы + * ставили вспышку руками, то есть красили экран дважды. + * Спецсобытие уровня зелий: там синие склянки забирают ПОЛОВИНУ + * запаса, а не одну единицу. */ + if (pop_current_level == POP_POTIONS_LEVEL) + hitp_delta = (int8_t)(-((hitp_max + 1) >> 1)); + else + hitp_delta = -1; + break; + default: /* 4 переворот / 6 открыть — TODO + * вместе с их уровнями (9/13) */ break; } } @@ -2556,6 +2592,10 @@ static void check_chomped_guard(void) static void kid_phys(void) { if (grab_timer) grab_timer--; + /* do_timers (seg003:0517): срок действия пера тикает раз в игровой кадр. + * Место — у Кида, а не у стража: эффект в оригинале снимается общим + * таймером главного цикла, а у нас единственный кадровый тик Кида. */ + if (pop_feather) pop_feather--; fall_accel(); fall_speed(); determine_col(); diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index ccd2a4a..8a1bbbf 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -84,8 +84,17 @@ extern uint8_t pop_item_taken; /* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока * НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */ extern uint8_t pop_flash_time; -/* Цвет вспышки: 0 = ярко-жёлтая (подобран меч), 1 = красная (зелье). */ -extern uint8_t pop_flash_red; +/* Цвет вспышки (порт flash_color, seg006 proc_get_object / seg000 feather_fall): + * жёлтая — подобран меч, красная — зелья HP, зелёная — перо. */ +#define POP_FLASH_YELLOW 0 +#define POP_FLASH_RED 1 +#define POP_FLASH_GREEN 2 +extern uint8_t pop_flash_color; + +/* is_feather_fall: кадров, пока действует зелье медленного падения. Читают + * play_seq (опкод JMP_IF_FEATHER — ветки stepfloat/bumpfloat в seqtbl) и + * приложение (сброс на старте уровня, seg003:189). */ +extern uint8_t pop_feather; uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч / 3 пить */ void pop_proc_get_object(void) __banked; /* эффект — из play_seq (опкод GET_ITEM) */ diff --git a/applications/PoP/roomtest/pop_room.c b/applications/PoP/roomtest/pop_room.c index 34e7a83..79bab84 100644 --- a/applications/PoP/roomtest/pop_room.c +++ b/applications/PoP/roomtest/pop_room.c @@ -646,11 +646,25 @@ void pop_potion_draw(int row, int col, uint8_t modif) __banked int pot_size = (type >= 2 && type < 5) ? 1 : 0; int x = (POP_COL_XH[col] + 3) * 8 + 1; int yb = 63 * row + 62 - (pot_size << 2) - 14; + uint8_t bubb; if (type == 0) return; + bubb = POTION_FRAM_BUBB[modif & 7]; + /* Цвет пузырька по ТИПУ зелья (draw_tile_anim, seg008:652): 3/4 (перо / + * переворот) — зелёный, 5/6 (вред / открыть ворота) — синий, остальные + * (1 лечение, 2 склянка жизни) — красный. У нас цвет запечён в спрайт + * при упаковке (mono-блиттера с параметром цвета нет), поэтому «цвет» = + * другой набор кадров: id 30..36 / 40..46 вместо 16..22 (pop_pack_bg.py). + * Кадр 0 («пузырька нет») общий — его подменять нечем и не нужно. */ + if (bubb) { + if (type == 3 || type == 4) + bubb = (uint8_t)(POP_POT_BUBB_GREEN + (bubb - POTION_FRAM_BUBB[1])); + else if (type == 5 || type == 6) + bubb = (uint8_t)(POP_POT_BUBB_BLUE + (bubb - POTION_FRAM_BUBB[1])); + } pop_heal_off(x - 1, yb - 10, 8, 12); gfx_set_bank(GFX_BANK_SPRITE); pop_pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */ - pop_pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */ + pop_pot_b(bubb, x, yb); /* кадр пузырька */ gfx_set_bank(GFX_BANK_NORMAL); } diff --git a/applications/PoP/roomtest/pop_tune.h b/applications/PoP/roomtest/pop_tune.h index 90ec176..57e9a5d 100644 --- a/applications/PoP/roomtest/pop_tune.h +++ b/applications/PoP/roomtest/pop_tune.h @@ -79,4 +79,20 @@ #define POP_DBG_SHADOW_NOWAIT 0 #endif +/* ---- Зелье МЕДЛЕННОГО ПАДЕНИЯ (SDLPoP: FEATHER_FALL_LENGTH) ---------- * + * В оригинале длительность эффекта задана НЕ таймером, а звуком: эффект + * держится, пока играет sound_39_low_weight, и снимается принудительно на + * 225-м тике (do_timers, seg003:0517). Звука у нас нет, поэтому берём тот + * же порог напрямую — 225 ИГРОВЫХ КАДРОВ (у оригинала тик do_timers = кадр + * игрового цикла; 225 кадров при его 12 fps и есть FEATHER_FALL_LENGTH = + * 18,75 с из фикса SDLPoP). По ВРЕМЕНИ у нас выйдет короче ровно настолько, + * насколько наша игра быстрее оригинала — это общий вопрос темпа + * (TASKS_OPEN.md, L1-SPEED), а не свойство зелья. */ +#define POP_FEATHER_FRAMES 225 + +/* ---- Уровень зелий (SDLPoP: «potions level», seg006:1894) ------------ * + * На нём синие склянки «вреда» забирают ПОЛОВИНУ запаса HP, а не единицу. + * В оригинале это условие `current_level == 15` прямо в proc_get_object. */ +#define POP_POTIONS_LEVEL 15 + #endif /* POP_TUNE_H */ diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 1fca257..c9672fb 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -443,6 +443,7 @@ static uint8_t pop_checkpoint; static void pop_start_level(void) { pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */ + pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */ uint8_t entry_pose = pop_level_entry_pose(pop_current_level); uint8_t start_room = pop_level_start_room(); uint8_t pos = pop_level_start_pos(); @@ -976,12 +977,13 @@ int main(void) * Приоритет как в оригинале: активный flash_time > кадр урона * (hitp_delta<0 -> ярко-красный, ровно на один кадр). */ if (pop_flash_time) { - /* Цвет вспышки: меч — ярко-жёлтый, зелье — красный (flash_color - * оригинала; нам хватает этих двух). */ - if (pop_flash_red) flash_bg(255, 0, 0); /* color_4_red */ - else flash_bg(255, 255, 85); /* color_14_brightyellow */ + /* Цвет вспышки (flash_color оригинала): меч — ярко-жёлтый, зелья + * HP — красный, перо (медленное падение) — зелёный. */ + if (pop_flash_color == POP_FLASH_RED) flash_bg(255, 0, 0); + else if (pop_flash_color == POP_FLASH_GREEN) flash_bg(85, 255, 85); + else flash_bg(255, 255, 85); pop_flash_time--; - if (!pop_flash_time) pop_flash_red = 0; + if (!pop_flash_time) pop_flash_color = POP_FLASH_YELLOW; flash_on = 1; } else if (pop_kid_hurt) { flash_bg(224, 0, 48); /* color_12: игра правит его на */ diff --git a/applications/PoP/roomtest/tests-host/t_phys.c b/applications/PoP/roomtest/tests-host/t_phys.c index dc0f391..cd45f40 100644 --- a/applications/PoP/roomtest/tests-host/t_phys.c +++ b/applications/PoP/roomtest/tests-host/t_phys.c @@ -21,6 +21,7 @@ #include "stubs.h" #include "pop_kid.h" #include "pop_map.h" +#include "pop_guard.h" /* pop_do_delta_hp — урон сводится дельтой */ /* Коды тайлов (pop_map): 0 пусто, 1 пол, 3 колонна, 11 loose, 20 стена. */ #define E 0 @@ -231,6 +232,56 @@ TC_TEST(phys_running_jump_over_3tile_gap) SC_CHECK(golden_rjump_gap3, "rjump_gap3"); } +/* Падение с ДВУХ рядов: пол только внизу, Кид стартует на кромке ряда 0. + * В обычном падении это −1 HP (land, seg005:173), под пером — ни урона, ни + * разгона. */ +static const uint8_t room_drop2[30] = { + W, F, E, E, E, E, E, E, E, E, + W, E, E, E, E, E, E, E, E, E, + W, F, F, F, F, F, F, F, F, W, +}; + +/* Максимальная скорость падения за прогон и итоговое HP — через сцену не + * видно (в трассу пишется поза), поэтому меряем прямо по Kid. */ +static uint8_t drop2_max_fall_y(uint8_t frames) +{ + uint8_t i, mx = 0; + for (i = 0; i < frames; i++) { + sc_trace_clear(); + sc_run(SC_R, 1); + if ((uint8_t)Kid.fall_y > mx) mx = (uint8_t)Kid.fall_y; + } + return mx; +} + +TC_TEST(phys_feather_fall_is_slow_and_harmless) +{ + uint8_t mx_normal, mx_feather, hp_normal, hp_feather; + + /* (1) как обычно: разгон до FALL_MAX и −1 HP за два ряда */ + sc_room(room_drop2, 1); + sc_kid_at(1, 0, 0); + pop_feather = 0; + mx_normal = drop2_max_fall_y(40); + pop_do_delta_hp(); /* урон копится дельтой, сводит её цикл */ + hp_normal = hitp_curr; + + /* (2) под зельем медленного падения (seg006:057C: ускорение 1, потолок 4; + * seqtbl уводит приземление в ветку stepfloat — без урона) */ + sc_room(room_drop2, 1); + sc_kid_at(1, 0, 0); + pop_feather = 200; + mx_feather = drop2_max_fall_y(40); + pop_do_delta_hp(); + hp_feather = hitp_curr; + pop_feather = 0; + + TC_TRUE(mx_normal > 4); /* обычное падение разгоняется */ + TC_TRUE(mx_feather <= 4); /* под пером потолок скорости — 4 */ + TC_TRUE(hp_normal < 3); /* два ряда обычно стоят HP */ + TC_EQ(hp_feather, 3); /* под пером — целым */ +} + /* ---- точка входа ----------------------------------------------------- */ void main(void) @@ -245,4 +296,5 @@ void main(void) TC_RUN(phys_run_off_ledge); TC_RUN(phys_loose_floor_breaks); TC_RUN(phys_running_jump_over_3tile_gap); + TC_RUN(phys_feather_fall_is_slow_and_harmless); } diff --git a/applications/PoP/toolchain/pop_pack_bg.py b/applications/PoP/toolchain/pop_pack_bg.py index 1ae531b..a5319bb 100644 --- a/applications/PoP/toolchain/pop_pack_bg.py +++ b/applications/PoP/toolchain/pop_pack_bg.py @@ -144,6 +144,15 @@ VGA16 = [(0,0,0),(0,0,42),(0,42,0),(0,42,42),(42,0,0),(42,0,42),(48,38,20), (63,21,63),(63,63,21),(63,63,63)] # 6-битные каналы, как в .pal POT_MASK_COLOR = VGA16_PAL_BASE + 0 # чёрный (blitters_40h_mono без цвета) POT_BUBBLE_COLOR = VGA16_PAL_BASE + 12 # ярко-красный (color 12: «лечение») +# Цвет пузырька зависит от ТИПА зелья (draw_tile_anim, seg008:652): типы 3/4 +# (перо / переворот) — зелёный, 5/6 (вред / открыть) — синий, остальные — +# красный. У нас спрайт красится при УПАКОВКЕ (mono-блиттера с параметром +# цвета в libbgi нет), поэтому каждый цвет — свой набор кадров: те же семь +# силуэтов под сдвинутыми id. Синий появится вместе с зельями 5/6. +POT_BUBBLE_GREEN_COLOR = VGA16_PAL_BASE + 10 # color 10 (seg008:663) +POT_BUBBLE_GREEN_BASE = 30 # id 30..36 = кадры 16..22 +POT_BUBBLE_BLUE_COLOR = VGA16_PAL_BASE + 9 # color 9 (seg008:658) +POT_BUBBLE_BLUE_BASE = 40 # id 40..46 = кадры 16..22 CHOMPER_BLOOD_COLOR = VGA16_PAL_BASE + 12 # тот же mono-цвет 12 (seg008:678) # Кладка ДВОРЦА (wall_pattern, seg008:1928, ветка palace): тело стены — @@ -475,6 +484,13 @@ def write_header(out_path, env_pages): "#endif /* POP_BG_ATLAS_NAMES */", '#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья, от набора не зависит */', "#define POP_PAL_POT 0x%02X" % POT_PAL_BASE, + "/* Пузырёк зелья: красный набор = id 16..22 (кадры оригинала),", + " зелёный (перо/переворот) и синий (вред/открыть) = те же кадры", + " под id %d..%d и %d..%d (draw_tile_anim, seg008:652). */" + % (POT_BUBBLE_GREEN_BASE, POT_BUBBLE_GREEN_BASE + len(POT_BUBBLE_IDS) - 1, + POT_BUBBLE_BLUE_BASE, POT_BUBBLE_BLUE_BASE + len(POT_BUBBLE_IDS) - 1), + "#define POP_POT_BUBB_GREEN %d" % POT_BUBBLE_GREEN_BASE, + "#define POP_POT_BUBB_BLUE %d" % POT_BUBBLE_BLUE_BASE, '#define POP_BG_PAL "pop_bg.pal"', "", "#endif", @@ -578,6 +594,11 @@ def main(): pot_entries[id] = load_potion(id, opaque=True) else: pot_entries[id] = load_potion(id) + # Наборы пузырьков ДРУГИХ цветов: те же кадры другим mono-цветом. + # зелёный — типы 3/4 (перо, переворот), синий — типы 5/6 (вред, открыть). + for i, id in enumerate(POT_BUBBLE_IDS): + pot_entries[POT_BUBBLE_GREEN_BASE + i] = load_potion(id, POT_BUBBLE_GREEN_COLOR) + pot_entries[POT_BUBBLE_BLUE_BASE + i] = load_potion(id, POT_BUBBLE_BLUE_COLOR) # chtab_1 (пламя/меч/зелья) от тайлсета НЕ зависит — пакуем один раз. cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_pot.atl"), pot_entries) total_bytes += sz