PoP roomtest: окно Char вокруг control() + общая шина ввода — база под ИИ стража
Инфраструктура пункта 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 <noreply@anthropic.com>
This commit is contained in:
@@ -75,13 +75,20 @@
|
|||||||
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
|
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
|
||||||
* кадрами (rest/save). */
|
* кадрами (rest/save). */
|
||||||
static int8_t control_x, control_y, control_shift;
|
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;
|
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;
|
||||||
|
|
||||||
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
|
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
|
||||||
* Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl
|
* Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl
|
||||||
* не дублируются в этот TU). */
|
* не дублируются в этот 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)
|
static int8_t release_arrows(void)
|
||||||
{
|
{
|
||||||
@@ -125,7 +132,7 @@ static void run_jump(void)
|
|||||||
{
|
{
|
||||||
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
|
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
|
||||||
* полировка K3; K2b просто запускает run-jump. */
|
* полировка K3; K2b просто запускает run-jump. */
|
||||||
if (Kid.frame >= FRAME_7_RUN) {
|
if (Char.frame >= FRAME_7_RUN) {
|
||||||
control_up = release_arrows();
|
control_up = release_arrows();
|
||||||
seqtbl_offset_char(SEQ_4_RUN_JUMP);
|
seqtbl_offset_char(SEQ_4_RUN_JUMP);
|
||||||
}
|
}
|
||||||
@@ -190,15 +197,15 @@ static void safe_step(void)
|
|||||||
control_forward = CONTROL_IGNORE;
|
control_forward = CONTROL_IGNORE;
|
||||||
distance = pop_edge_distance();
|
distance = pop_edge_distance();
|
||||||
if (distance) {
|
if (distance) {
|
||||||
Kid.repeat = 1;
|
Char.repeat = 1;
|
||||||
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
|
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
|
||||||
} else if (pop_edge_type() == EDGE_WALL) {
|
} else if (pop_edge_type() == EDGE_WALL) {
|
||||||
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
|
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
|
||||||
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
|
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
|
||||||
Kid.repeat = 1;
|
Char.repeat = 1;
|
||||||
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
|
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
|
||||||
} else if (Kid.repeat != 0) {
|
} else if (Char.repeat != 0) {
|
||||||
Kid.repeat = 0;
|
Char.repeat = 0;
|
||||||
seqtbl_offset_char(SEQ_44_STEP_ON_EDGE);
|
seqtbl_offset_char(SEQ_44_STEP_ON_EDGE);
|
||||||
} else {
|
} else {
|
||||||
seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */
|
seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */
|
||||||
@@ -266,7 +273,7 @@ static void control_running(void)
|
|||||||
/* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок
|
/* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок
|
||||||
* seq_47 при беге в стену (как оригинал), с корректной позицией. */
|
* seq_47 при беге в стену (как оригинал), с корректной позицией. */
|
||||||
if (control_x == CONTROL_RELEASED &&
|
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();
|
control_forward = release_arrows();
|
||||||
seqtbl_offset_char(SEQ_13_STOP_RUN);
|
seqtbl_offset_char(SEQ_13_STOP_RUN);
|
||||||
} else if (control_x == CONTROL_HELD_BACKWARD) {
|
} else if (control_x == CONTROL_HELD_BACKWARD) {
|
||||||
@@ -318,16 +325,16 @@ static void control_jumpup(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */
|
/* 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) и в свободном падении управление
|
/* seg005 control(): в отскоке (bumped) и в свободном падении управление
|
||||||
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
|
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
|
||||||
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
|
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
|
||||||
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
|
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
|
||||||
* стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
|
* стартовый 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();
|
release_arrows();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -406,12 +413,12 @@ static void flip_control_x(void)
|
|||||||
/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */
|
/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */
|
||||||
static void user_control(void)
|
static void user_control(void)
|
||||||
{
|
{
|
||||||
if (Kid.direction >= DIR_RIGHT) {
|
if (Char.direction >= DIR_RIGHT) {
|
||||||
flip_control_x();
|
flip_control_x();
|
||||||
control();
|
pop_control();
|
||||||
flip_control_x();
|
flip_control_x();
|
||||||
} else {
|
} else {
|
||||||
control();
|
pop_control();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,6 +458,12 @@ void pop_ctrl_tick(void)
|
|||||||
rest_ctrl_1();
|
rest_ctrl_1();
|
||||||
read_input();
|
read_input();
|
||||||
read_user_control();
|
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();
|
save_ctrl_1();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,19 @@ void pop_ctrl_tick(void);
|
|||||||
* зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */
|
* зацеп за уступ в падении по Shift. Валидно после pop_ctrl_tick. */
|
||||||
int8_t pop_ctrl_shift_held(void);
|
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
|
#endif
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ pop_char_t Char;
|
|||||||
|
|
||||||
void pop_loadkid(void) { Char = Kid; }
|
void pop_loadkid(void) { Char = Kid; }
|
||||||
void pop_savekid(void) { Kid = Char; kid_frame = cur_frame; }
|
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_loadshad(void) { Char = Guard; }
|
||||||
void pop_saveshad(void) { Guard = Char; pop_gframe = cur_frame; }
|
void pop_saveshad(void) { Guard = Char; pop_gframe = cur_frame; }
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ void play_seq(void); /* работает с Char — см. pop_kid_play
|
|||||||
extern pop_char_t Char;
|
extern pop_char_t Char;
|
||||||
void pop_loadkid(void);
|
void pop_loadkid(void);
|
||||||
void pop_savekid(void);
|
void pop_savekid(void);
|
||||||
|
void pop_savekid_state(void); /* Kid = Char, кадр не трогаем */
|
||||||
void pop_loadshad(void);
|
void pop_loadshad(void);
|
||||||
void pop_saveshad(void);
|
void pop_saveshad(void);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user