From cf06896dbdccc2226676e7a66434272386315109 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 30 Jul 2026 10:32:15 +0300 Subject: [PATCH] =?UTF-8?q?PoP=20roomtest:=20=D0=BE=D0=BA=D0=BD=D0=BE=20Ch?= =?UTF-8?q?ar=20=D0=B2=D0=BE=D0=BA=D1=80=D1=83=D0=B3=20control()=20+=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B0=D1=8F=20=D1=88=D0=B8=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B2=D0=B2=D0=BE=D0=B4=D0=B0=20=E2=80=94=20=D0=B1=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=D0=B4=20=D0=98=D0=98=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=B6=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Инфраструктура пункта 2. В оригинале у соперника НЕТ своего диспетчера: autocontrol_* (seg002) выставляет те же глобалы control_*, что и ввод игрока, а дальше исполняется тот же control() (seg005:252). Значит перед портом ИИ надо было привести к этому обе половины: - control_forward/backward/up/down/shift2 перестали быть static в pop_ctrl.c — это общая шина синтетического ввода, объявлена в pop_ctrl.h вместе с POP_CONTROL_*; - control() переименован в pop_control() и работает с Char, а не с Kid; - ввод игрока обёрнут в окно Char (loadkid/user_control/savekid), как в play_frame оригинала. Ловушка по дороге (ввод отвалился целиком, Kid не двигался): макрос seqtbl_offset_char вёл на kid_set_seq, который пишет прямо в Kid, а следом savekid затирал Kid копией Char со старой curr_seq. В оригинале seqtbl_offset_char работает именно с Char — макрос переведён на pop_char_set_seq. kid_set_seq остался для вызовов ВНЕ окна (pop_map). Добавлен pop_savekid_state (Kid = Char без кадра): control() кадр не трогает, а cur_frame в этот момент принадлежит тому, кто последним крутил play_seq. Проверено в MAME: бег и упор в стену работают как прежде. Бюджет (комната 3, 125 кадров): 382 584..383 064 против 380 292..381 282, то есть +2 300 тактов на копии окна. 0.891 кадра, запас 46 936. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/pop_ctrl.c | 43 ++++++++++++++++++---------- applications/PoP/roomtest/pop_ctrl.h | 15 ++++++++++ applications/PoP/roomtest/pop_kid.c | 3 ++ applications/PoP/roomtest/pop_kid.h | 1 + 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/applications/PoP/roomtest/pop_ctrl.c b/applications/PoP/roomtest/pop_ctrl.c index 9e435ae..f28b6ba 100644 --- a/applications/PoP/roomtest/pop_ctrl.c +++ b/applications/PoP/roomtest/pop_ctrl.c @@ -75,13 +75,20 @@ * автомат held/released/ignore (авто-повтор); ctrl1_* — их память между * кадрами (rest/save). */ static int8_t control_x, control_y, control_shift; -static int8_t control_forward, control_backward, control_up, control_down, control_shift2; +/* НЕ static: в оригинале это глобалы, через которые ИИ стража подаёт + * СИНТЕТИЧЕСКИЙ ввод (autocontrol_*, seg002), а дальше работает тот же + * control(), что и у игрока. Поэтому шина общая. */ +int8_t control_forward, control_backward, control_up, control_down, control_shift2; static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2; /* ---- seqtbl_offset_char: сменить последовательность Kid ------------ * * Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl * не дублируются в этот TU). */ -#define seqtbl_offset_char(seq) kid_set_seq(seq) +/* В оригинале seqtbl_offset_char работает с АКТИВНЫМ персонажем, и + * control() вызывается внутри окна Char. Раньше макрос вёл на + * kid_set_seq (пишет прямо в Kid) — после введения окна это ломало ввод: + * savekid затирал Kid копией Char со старой curr_seq. */ +#define seqtbl_offset_char(seq) pop_char_set_seq(seq) static int8_t release_arrows(void) { @@ -125,7 +132,7 @@ static void run_jump(void) { /* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это * полировка K3; K2b просто запускает run-jump. */ - if (Kid.frame >= FRAME_7_RUN) { + if (Char.frame >= FRAME_7_RUN) { control_up = release_arrows(); seqtbl_offset_char(SEQ_4_RUN_JUMP); } @@ -190,15 +197,15 @@ static void safe_step(void) control_forward = CONTROL_IGNORE; distance = pop_edge_distance(); if (distance) { - Kid.repeat = 1; + Char.repeat = 1; seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE)); } else if (pop_edge_type() == EDGE_WALL) { /* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад * (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */ - Kid.repeat = 1; + Char.repeat = 1; seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE)); - } else if (Kid.repeat != 0) { - Kid.repeat = 0; + } else if (Char.repeat != 0) { + Char.repeat = 0; seqtbl_offset_char(SEQ_44_STEP_ON_EDGE); } else { seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */ @@ -266,7 +273,7 @@ static void control_running(void) /* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок * seq_47 при беге в стену (как оригинал), с корректной позицией. */ if (control_x == CONTROL_RELEASED && - (Kid.frame == FRAME_7_RUN || Kid.frame == FRAME_11_RUN)) { + (Char.frame == FRAME_7_RUN || Char.frame == FRAME_11_RUN)) { control_forward = release_arrows(); seqtbl_offset_char(SEQ_13_STOP_RUN); } else if (control_x == CONTROL_HELD_BACKWARD) { @@ -318,16 +325,16 @@ static void control_jumpup(void) } /* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */ -static void control(void) +void pop_control(void) { - uint8_t f = Kid.frame; + uint8_t f = Char.frame; /* seg005 control(): в отскоке (bumped) и в свободном падении управление * НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок * прерывает присед после удара (medland/плита по голове) нажатием ↑, и * последовательность не доигрывает свои dy(1)+dy(1), компенсирующие * стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на * пару пикселей меньше, чем должен»). */ - if (Kid.action == 5 || Kid.action == 4) { /* bumped / in_freefall */ + if (Char.action == 5 || Char.action == 4) { /* bumped / in_freefall */ release_arrows(); return; } @@ -406,12 +413,12 @@ static void flip_control_x(void) /* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */ static void user_control(void) { - if (Kid.direction >= DIR_RIGHT) { + if (Char.direction >= DIR_RIGHT) { flip_control_x(); - control(); + pop_control(); flip_control_x(); } else { - control(); + pop_control(); } } @@ -451,6 +458,12 @@ void pop_ctrl_tick(void) rest_ctrl_1(); read_input(); read_user_control(); - user_control(); /* -> control(): может сменить Kid.curr_seq */ + /* Окно Char вокруг диспетчера: control() работает с АКТИВНЫМ + * персонажем (как в оригинале), поэтому ввод игрока применяется к + * Киду только внутри loadkid/savekid. Кадр не перезагружаем — + * play_seq в kid_tick сделает это следующим шагом. */ + pop_loadkid(); + user_control(); /* -> pop_control(): может сменить Char.curr_seq */ + pop_savekid_state(); save_ctrl_1(); } diff --git a/applications/PoP/roomtest/pop_ctrl.h b/applications/PoP/roomtest/pop_ctrl.h index f3a41a2..c524eb4 100644 --- a/applications/PoP/roomtest/pop_ctrl.h +++ b/applications/PoP/roomtest/pop_ctrl.h @@ -32,4 +32,19 @@ void pop_ctrl_tick(void); * зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */ int8_t pop_ctrl_shift_held(void); +/* ---- Шина синтетического ввода (порт глобалов control_* из seg005) ---- + * В оригинале ИИ соперника (autocontrol_*, seg002) не имеет своего + * диспетчера: он ВЫСТАВЛЯЕТ эти переменные, а дальше исполняется тот же + * control(), что и для игрока. Значения — CONTROL_RELEASED/IGNORE/HELD. */ +#define POP_CONTROL_RELEASED 0 +#define POP_CONTROL_IGNORE 1 +#define POP_CONTROL_HELD (-1) +extern int8_t control_forward, control_backward, control_up, control_down, + control_shift2; + +/* Диспетчер (seg005:252) для АКТИВНОГО персонажа Char. Игрок идёт через + * pop_ctrl_tick (тот делает user_control с зеркалом «вперёд»), страж — из + * своего тика напрямую. */ +void pop_control(void); + #endif diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index 9e16535..680c16d 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -134,6 +134,9 @@ pop_char_t Char; void pop_loadkid(void) { Char = Kid; } void pop_savekid(void) { Kid = Char; kid_frame = cur_frame; } +/* Выгрузка БЕЗ кадра: control() кадр не трогает, а cur_frame в этот + * момент принадлежит тому, кто последним крутил play_seq. */ +void pop_savekid_state(void) { Kid = Char; } void pop_loadshad(void) { Char = Guard; } void pop_saveshad(void) { Guard = Char; pop_gframe = cur_frame; } diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index 52a8865..4714c84 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -61,6 +61,7 @@ void play_seq(void); /* работает с Char — см. pop_kid_play extern pop_char_t Char; void pop_loadkid(void); void pop_savekid(void); +void pop_savekid_state(void); /* Kid = Char, кадр не трогаем */ void pop_loadshad(void); void pop_saveshad(void);