BUG-CTRL-FRAME-1: геометрия Кида считалась по кадру стража

cur_frame — один глобал на всех персонажей (как в оригинале), владелец —
тот, кто последним прошёл load_frame.  Последним в кадре тикает страж,
поэтому к моменту control() Кида там лежал кадр СТРАЖА, а через
kid_cur_dx/dy/flags по нему считается вся геометрия управления:
dx_weight -> determine_col -> distance_to_edge_weight -> get_edge_distance
-> выбор ветки в check_jump_up.

Оригинал зовёт load_fram_det_col() (seg006:0144) сразу после
loadkid/loadshad и ДО control() — play_kid_frame (seg000:1211) и
play_guard_frame (seg000:1248).  У нас этого не было.

Замерено брейкпоинтом на pop_jump_up_seq: Kid x=156 col=6 кадр 15
(dx=0 weight_x=3) при кадре стража image17 (dx=-1 weight_x=8) дал
curr_col=7 и distance=2 вместо 6 и 10 — то есть jump_up_plain (вернулось
A=28, пустой прыжок) вместо «шаг назад на x=160 + зацеп».  Предсказание
по кадру стража совпало с намеренным до единицы.

Отсюда же плавающее поведение: кадр стража меняется каждый тик, distance
Кида скакал через порог 6 — то прыжок, то попытка зацепа с неверной X.
И «голова Кида поверх плиты (1,6)» — не баг отрисовки, а следствие позы,
которой в оригинале в этом месте не бывает.

Фикс: pop_load_fram_det_col() (pop_kid.c) + вызовы в pop_ctrl_tick и
pop_guard_tick.  determine_col — только на ветке Кида: у нас он
существует лишь для него (pop_map работает с Kid, а не с Char).

Разбор с числами — bug_closed.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-04 12:55:43 +03:00
parent 5353bdaaec
commit e67117219f
7 changed files with 227 additions and 3 deletions
+21 -1
View File
@@ -31,6 +31,7 @@
#include "pop_state.h"
#include "pop_trob.h" /* room_modif + trob-список (пики/ворота) */
#include "pop_level.h" /* pop_level_start_room — вход в дверь уровня */
#include "pop_guard.h" /* SWORD_2_DRAWN — ветка приземления с мечом */
/* ---- Тайлы / действия / seq id (подмножество PoP) ------------------ */
#define TILE_EMPTY 0
@@ -80,6 +81,7 @@
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
#define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */
#define SEQ_63_ACTIVE_AFTER_FALL 63 /* стойка С МЕЧОМ после приземления */
#define TILE_FLOOR 1
#define FRAME_109_CROUCH 109
@@ -365,6 +367,11 @@ static void determine_col(void)
Kid.curr_col = get_tile_div_mod_m7(dx_weight());
}
/* determine_col наружу — для pop_load_fram_det_col (pop_kid): порт связки
* load_frame + determine_col (seg006:0144), которую оригинал зовёт перед
* control(). См. разбор там. */
void pop_determine_col(void) __banked { determine_col(); }
/* расстояние до края тайла (для in_wall). */
static int distance_to_edge(int xpos)
{
@@ -478,7 +485,20 @@ static void land(void)
* приложение рисует «брызги» И даёт КРАСНУЮ вспышку фона (flash_if_hurt,
* seg003:0AFC). */
if (Kid.fall_y < 22) {
seq = SEQ_17_SOFT_LAND;
/* seg005:176 — ВЕТКА С МЕЧОМ, и она не косметика. seq_17_soft_land
* заканчивается ВЕЧНЫМ циклом на кадре 109 (`jmp softland_crouch` в
* seqtbl), из которого выводит только control_crouched(). А
* control() при `Char.sword == sword_2_drawn` до него не доходит:
* управление уходит в control_with_sword, который знает лишь кадр
* 171 (стойка с мечом). То есть Кид, упавший на этаж С ВЫНУТЫМ
* МЕЧОМ, в этот присед садится НАВСЕГДА — ровно то, что поймали на
* приёмке уровня 2 (BUG-LAND-SWORD-1). Оригинал такой позы не
* допускает: с мечом приземление даёт боевую стойку. */
if (Kid.sword == SWORD_2_DRAWN) {
seq = SEQ_63_ACTIVE_AFTER_FALL;
} else {
seq = SEQ_17_SOFT_LAND;
}
} else {
uint8_t deadly = (uint8_t)(Kid.fall_y >= 33); /* СНЯТЬ до обнуления */
pop_kid_hurt = 1;