diff --git a/applications/PoP/docs/l9_invert_plan.md b/applications/PoP/docs/l9_invert_plan.md index 5f45b91..d9d53fb 100644 --- a/applications/PoP/docs/l9_invert_plan.md +++ b/applications/PoP/docs/l9_invert_plan.md @@ -170,7 +170,32 @@ void gfx_copy_rect(uint8_t src_page, int sx, int sy, # Часть II — приложение (roomtest) -## II.1 Состояние и момент переключения +## II.1 Состояние и момент переключения — СДЕЛАНО 2026-08-12 + +Проверено в MAME (уровень 9, комната 7): нажатие чита переворачивает всю +комнату целиком за один кадр, повторное — возвращает. Персонажи и +анимируемые тайлы (факелы) пока рисуются НЕ зеркально — это шаги II.3/II.4. + +Что появилось: + +- `pop_upside` + `pop_upside_dirty` (порт `upside_down` и + `need_redraw_because_flipped`) в `pop_map.c`; +- ветка `case 4` в `pop_proc_get_object`: только тоггл, без вспышки и урона + (как seg006:1885); +- сброс в `pop_start_level` (seg003:38/188) и по смерти Кида (seg000:1224); +- `pop_flip_screen()` в банке 8: `gfx_copy_page(VFLIP)` в скрытую страницу → + показать её → `gfx_copy_page(DIRECT)` во вторую → инвалидация слотов + персонажей, метки «фон трогали» и полосы HP; +- **чит-клавиша U** — это ПОРТ, а не костыль: в оригинале переворот тоже + висит на чит-клавише (seg000:0793). Без неё эффект проверяется только + честным проходом уровня 9 до комнаты 7 — чит-телепорт до зелья не + дотягивается (оно на `(1,7)`, а навигация ставит Кида на нижний ряд). + +Цена: резидент +602 Б (новые функции libbgi линкуются в него), куча +1707 → 1050 Б. Это аргумент за [MEM-COLD2](../roomtest/TASKS_OPEN.md#mem-cold2) +до начала II.4. + +### Контракт (исходное описание шага) - `pop_upside` (uint8_t) — рядом с `pop_feather` в `pop_map.c`, экспорт в `pop_map.h`; diff --git a/applications/PoP/roomtest/pop_cheat.h b/applications/PoP/roomtest/pop_cheat.h index 8acb53f..e5f05b5 100644 --- a/applications/PoP/roomtest/pop_cheat.h +++ b/applications/PoP/roomtest/pop_cheat.h @@ -7,7 +7,8 @@ * ВКЛЮЧЁН). Клавиши читов обрабатывает главный цикл. * * Реализовано: K — убить стража (seg000:786), I/S/Shift+L (наши отладочные), - * [ / ] — подгонка Кида по X (seg000:1828). Остальные читы оригинала + * [ / ] — подгонка Кида по X (seg000:1828), U — переворот экрана + * (seg000:793). Остальные читы оригинала * (время, перо, воскрешение, переход по комнатам) пока не портированы. */ #ifndef POP_CHEAT_H @@ -23,6 +24,10 @@ extern uint8_t pop_cheats; /* 0 = выключены, иначе включ #define KBD_CHEAT_NEXTLVL 0x4B /* Shift+L (PS/2 set 2) — следующий уровень */ #define KBD_CHEAT_XDEC 0x54 /* [ (PS/2 set 2) — сдвинуть Кида на 1 px влево */ #define KBD_CHEAT_XINC 0x5B /* ] (PS/2 set 2) — сдвинуть Кида на 1 px вправо */ +#define KBD_CHEAT_UPSIDE 0x3C /* U (PS/2 set 2) — переворот экрана (toggle_upside, + * seg000:0793: в оригинале это тоже ЧИТ-клавиша). + * Без него зелье инверсии проверяется только + * честным проходом уровня 9 до комнаты 7. */ /* Последний уровень игры (tbl_* в pop_level рассчитаны на 0..15). */ #define POP_LAST_LEVEL 15 diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index bed36f1..8b52cc6 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -167,6 +167,16 @@ static uint8_t grab_timer; /* после зацепа (12): блокиру * просто обратный отсчёт кадров (POP_FEATHER_FRAMES, pop_tune.h). Публичный: * его читают play_seq (опкод JMP_IF_FEATHER, pop_kid.c) и приложение. */ uint8_t pop_feather; +/* upside_down (seg000:15E9 toggle_upside): 1 — картинка перевёрнута вверх + * ногами (зелье типа 4, уровень 9). Держим ЗДЕСЬ, рядом с пером: оба + * ставятся из одной ветки proc_get_object и оба сбрасываются стартом + * уровня. Читает отрисовка (банки 2/4/7) и главный цикл — отсюда + * публичность. Смерть Кида снимает эффект (seg000:1224). */ +uint8_t pop_upside; +/* need_redraw_because_flipped (seg000:15E9/1226): переворот ТОЛЬКО ЧТО + * переключился, и уже нарисованное надо привести в новый вид. Разбирает + * главный цикл (pop_flip_screen) и сам снимает флаг. */ +uint8_t pop_upside_dirty; /* Состояние edge-триггера бампа — массивы флагов перекрытия по колонкам * (порт prev_coll_flags/curr_row_coll_flags, seg004); объявлены ниже, рядом * с check_collisions. Сброс при смене комнаты — coll_prev_row. */ @@ -1260,6 +1270,15 @@ void pop_proc_get_object(void) __banked pop_flash_color = POP_FLASH_GREEN; pop_flash_time = 3; break; + case 4: /* ПЕРЕВОРОТ: картинка вверх ногами */ + /* toggle_upside (seg000:15E9): ВЕСЬ эффект — инверсия флага и + * пометка «перерисовать, потому что перевернулись». Ни вспышки, + * ни звука, ни урона у этой ветки нет (seg006:1885). Сам + * переворот уже нарисованного делает приложение + * (pop_flip_screen, roomtest_cold.c). */ + pop_upside = (uint8_t)!pop_upside; + pop_upside_dirty = 1; + break; case 5: /* «злое» зелье (синее): −1 HP */ /* seg006:1892. Своей вспышки у этой ветки НЕТ (в отличие от * лечебных): красный кадр даёт общий механизм «Кид ранен» @@ -1272,8 +1291,8 @@ void pop_proc_get_object(void) __banked else hitp_delta = -1; break; - default: /* 4 переворот / 6 открыть — TODO - * вместе с их уровнями (9/13) */ + default: /* 6 «открыть все ворота» — TODO + * вместе с его уровнем (13) */ break; } } @@ -2606,6 +2625,10 @@ static void kid_phys(void) check_spike_below(); /* seg006: над колонкой с пиками → выдвинуть пики */ check_spiked(); /* seg006: напоролся на вредные пики → смерть */ check_chomped_kid(); /* seg004: перемололо в сомкнутых челюстях → смерть */ + /* upside_down снимается смертью (seg000:1224: `if (upside_down && + * Char.alive >= 0)`). У нас признак смерти — pop_kid_dead, ставится + * там же, где оригинал переводит alive в >= 0. */ + if (pop_kid_dead && pop_upside) { pop_upside = 0; pop_upside_dirty = 1; } if (pop_kid_dead) return; /* только что умер — без leave/knock в этот кадр */ pop_check_knock(); /* seg003: сотрясение приземления → тряска loose в ряду */ /* exit_room_timer (seg002 exit_room): 2 кадра ПОСЛЕ входа в комнату любой diff --git a/applications/PoP/roomtest/pop_map.h b/applications/PoP/roomtest/pop_map.h index 8a1bbbf..cd6bfcc 100644 --- a/applications/PoP/roomtest/pop_map.h +++ b/applications/PoP/roomtest/pop_map.h @@ -95,6 +95,15 @@ extern uint8_t pop_flash_color; * play_seq (опкод JMP_IF_FEATHER — ветки stepfloat/bumpfloat в seqtbl) и * приложение (сброс на старте уровня, seg003:189). */ extern uint8_t pop_feather; + +/* upside_down (seg000:15E9): 1 — картинка перевёрнута вверх ногами (зелье + * типа 4 на уровне 9). Читают все слои отрисовки; сбрасывается стартом + * уровня и смертью Кида (seg000:1224). + * pop_upside_dirty — порт need_redraw_because_flipped: флаг «состояние + * переворота ИЗМЕНИЛОСЬ», по нему главный цикл один раз переворачивает уже + * нарисованное (pop_flip_screen) и снимает флаг. */ +extern uint8_t pop_upside; +extern uint8_t pop_upside_dirty; 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/roomtest.c b/applications/PoP/roomtest/roomtest.c index 89186d8..db7a225 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -431,7 +431,7 @@ int main(void) uint8_t nav_prev = 0; #endif uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0; - uint8_t xdec_prev = 0, xinc_prev = 0; + uint8_t xdec_prev = 0, xinc_prev = 0, upsd_prev = 0; /* Набор фона — по типу СТАРТОВОГО уровня. Атласы грузятся ДО initgraph * (тот снимает DSS-страницу W0); уровень к этому моменту ещё не прочитан, @@ -540,6 +540,7 @@ int main(void) uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD); uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) && (kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))); + uint8_t up = kbd_raw_down(KBD_CHEAT_UPSIDE); uint8_t xd = kbd_raw_down(KBD_CHEAT_XDEC); uint8_t xi = kbd_raw_down(KBD_CHEAT_XINC); if (pop_cheats && k && !cheat_prev) pop_guard_kill(); @@ -547,12 +548,19 @@ int main(void) if (pop_cheats && s && !sword_prev) pop_have_sword = 1; if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL) pop_next_level = (uint8_t)(pop_current_level + 1); + /* U — переворот экрана (seg000:0793 — чит и в оригинале). + * Тот же путь, что у зелья типа 4: флаг + пометка «перевернулись». */ + if (pop_cheats && up && !upsd_prev) { + pop_upside = (uint8_t)!pop_upside; + pop_upside_dirty = 1; + } if (pop_cheats && xd && !xdec_prev) Kid.x = (uint8_t)(Kid.x - 1); if (pop_cheats && xi && !xinc_prev) Kid.x = (uint8_t)(Kid.x + 1); cheat_prev = k; immo_prev = i; sword_prev = s; nlvl_prev = nl; + upsd_prev = up; xdec_prev = xd; xinc_prev = xi; } @@ -580,6 +588,17 @@ int main(void) back = dbuf ? (uint8_t)(front ^ 1) : 0; gfx_set_draw_page(back); + /* Переворот только что переключился (зелье инверсии, ур. 9) — + * привести уже нарисованное в новый вид ОДИН раз, до кадра + * (порт need_redraw_because_flipped, seg000:939). Дальше слои + * рисуют зеркально сами. */ + if (pop_upside_dirty) { + pop_upside_dirty = 0; + pop_flip_screen(); + front = gfx_get_visible_page(); + back = dbuf ? (uint8_t)(front ^ 1) : 0; + gfx_set_draw_page(back); + } pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */ pop_check_mouse(); /* спецсобытие ур.8: мышь жмёт кнопку */ pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */ diff --git a/applications/PoP/roomtest/roomtest_cold.c b/applications/PoP/roomtest/roomtest_cold.c index 7f77ae6..315e33d 100644 --- a/applications/PoP/roomtest/roomtest_cold.c +++ b/applications/PoP/roomtest/roomtest_cold.c @@ -19,6 +19,8 @@ #include "pop_guard.h" #include "pop_state.h" #include "pop_geom.h" +#include "pop_cdraw.h" /* pop_cd — инвалидация слотов после переворота */ +#include /* gfx_copy_page */ #include "pop_tune.h" #define SEQ_STAND 2 @@ -73,6 +75,11 @@ void pop_start_level(void) __banked pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */ pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */ + /* upside_down тоже сбрасывается стартом уровня (seg003:38/188). Если + * картинка была перевёрнута, отрисовка комнаты ниже уже пойдёт обычной + * стороной, поэтому переворачивать ничего не надо — только снять флаги. */ + pop_upside = 0; + pop_upside_dirty = 0; entry_pose = pop_level_entry_pose(pop_current_level); start_room = pop_level_start_room(); pos = pop_level_start_pos(); @@ -247,3 +254,49 @@ void pop_dbg_room_label(uint8_t room) __banked for (i = 0; i < (uint8_t)(room % 10); i++) bar(130 + i * 5, 0, 131 + i * 5, 5); room_label_redraw--; } + +/* ---- Переворот уже нарисованного (зелье инверсии, уровень 9) --------- * + * + * Порт need_redraw_because_flipped (seg000:15E9 + 939/946). Оригинал + * держит готовый offscreen и переворачивает его на выводе; у нас страниц + * ровно две, рабочей третьей нет, поэтому переворачиваем то, что уже + * нарисовано, — построчной копией акселератора (gfx_copy_page). + * + * Почему это законно и почему heal после не ломается: accel ЧИТАЕТ + * ОЗУ-копию, значит копируется ЧИСТЫЙ ФОН без персонажей, а пишет банком + * 0x50 — и в видео, и в копию приёмника. После двух проходов обе страницы + * дабл-буфера содержат перевёрнутый фон в обеих плоскостях, и heal + * восстанавливает уже его (memory accel_port_y_between_triggers, + * регресс — tests/pageflip). + * + * Порядок: перевернуть в СКРЫТУЮ, показать её, скопировать оттуда во + * вторую (теперь скрытую) — прямой копией. Так игрок видит результат + * сразу, а не после второго прохода. + * + * Переворачиваем ТОЛЬКО игровое поле (POP_PLAYFIELD_H): полоса HP лежит + * ниже него и в оригинале тоже не переворачивается. */ +void pop_flip_screen(void) __banked +{ + gfx_rect_t area; + uint8_t back = (uint8_t)(gfx_get_visible_page() ^ 1); + + area.x = 0; area.y = POP_YOFF; + area.w = 320; area.h = POP_PLAYFIELD_H; + + gfx_set_draw_page(back); + gfx_copy_page(&area, GFX_COPY_VFLIP); /* источник — видимая страница */ + gfx_set_visible_page(back); + + gfx_set_draw_page((uint8_t)(back ^ 1)); + gfx_copy_page(&area, GFX_COPY_DIRECT); /* вторую страницу — прямой копией */ + + /* Всё, что рисуется поверх фона, надо положить заново: прошлые + * прямоугольники персонажей относятся к СТАРОЙ (неперевёрнутой) + * картинке, и heal по ним стёр бы не то место. */ + pop_cd[POP_CH_KID].valid[0] = pop_cd[POP_CH_KID].valid[1] = 0; + pop_cd[POP_CH_KID].ovalid[0] = pop_cd[POP_CH_KID].ovalid[1] = 0; + pop_cd[POP_CH_OPP].valid[0] = pop_cd[POP_CH_OPP].valid[1] = 0; + pop_cd[POP_CH_OPP].ovalid[0] = pop_cd[POP_CH_OPP].ovalid[1] = 0; + POP_CD_TOUCH_ALL(); /* фон трогали целиком */ + pop_hp_invalidate(); /* полосу HP перерисовать */ +} diff --git a/applications/PoP/roomtest/roomtest_cold.h b/applications/PoP/roomtest/roomtest_cold.h index b175993..cadcb65 100644 --- a/applications/PoP/roomtest/roomtest_cold.h +++ b/applications/PoP/roomtest/roomtest_cold.h @@ -50,4 +50,9 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked; * звать можно каждый кадр. */ void pop_dbg_room_label(uint8_t room) __banked; +/* Перевернуть уже нарисованное (зелье инверсии, уровень 9): две копии + * акселератором + инвалидация слотов персонажей и полосы HP. Звать РАЗ на + * переключение, по флагу pop_upside_dirty. */ +void pop_flip_screen(void) __banked; + #endif /* ROOMTEST_COLD_H */