Трасса kidobj для сверки с SDLPoP + разбор боя у шва
Инструмент: pop_dbg_kidobj печатает то же, что вывод, добавленный
пользователем в SDLPoP add_kid_to_objtable — tilepos/frame/act/col/cols/rows
(+ наша комната). Считает по set_char_collision и set_objtile_at_char.
Выключен по умолчанию (DBG_KIDOBJ 0), включается одним define; вывод
забирает брейкпоинт MAME на резидентном pop_dbg_trap.
Сверка подтвердила фикс a498255: окно перехода совпало кадр в кадр, за бой
на уровне 1 комната сменилась один раз, на уровне 8 у шва 24/18 — четыре
раза на 387 кадров боя, и все четыре на РАЗРЕШЁННЫХ кадрах (170/164/170/165).
Смен на запрещённых кадрах во всей трассе нет.
Разбор целиком — BUGS_CLOSED.md#seam-fight-flicker, включая невыясненное
расхождение поля cols.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3152,3 +3152,89 @@ else died_on_button(...); /* мёртв — кнопка С
|
||||
Вероятнее всего это кадр уже НАЧАВШЕГОСЯ падения (при падении X сносит), но
|
||||
доказательства нет. Если `(0,-1)` появится на исправленном билде — это
|
||||
отдельный баг.
|
||||
|
||||
---
|
||||
|
||||
<a id="seam-fight-flicker"></a>
|
||||
## SEAM-FIGHT-FLICKER. Бой у шва: комната перерисовывалась то одна, то другая
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-18).** Уровень 8, шов комнат 24/18: во
|
||||
время схватки со стражем экран постоянно переключался между комнатами —
|
||||
«драться очень неудобно». Отдельно пользователь заметил, что уходит Кид,
|
||||
даже когда просто держит «вверх» (защита), и предположил верную причину:
|
||||
стойка защиты не одна, Кид периодически проходит через обычную.
|
||||
|
||||
**Корень.** `leave_room` (seg002:0490) запрещает смену комнаты не только на
|
||||
развороте, подъёме-с-зацепа (135..149) и вставании из приседа (110..119), но
|
||||
и на ВСЕЙ боевой анимации — кадры **150..162** и **166..168**. У нас были
|
||||
только первые три условия.
|
||||
|
||||
Разрешены при этом 163..165, 169 (`begin_block`) и 170/171 (`stand with
|
||||
sword`) — то есть уйти можно, но только на «легальном» кадре. Цикл защиты
|
||||
(seqtbl: `readyblock` = 169 -> `blocking` = 150, петля) как раз проходит
|
||||
через 169, а между атаками Кид возвращается в 158/170 — отсюда и ощущение
|
||||
«стоять в защите нельзя».
|
||||
|
||||
Побочный эффект известен и в оригинале эксплуатируется — **Trick 35,
|
||||
«retreat without leaving the room»**: определённым ритмом «назад» кадр 170 не
|
||||
наступает никогда, и Кид пятится из комнаты, не переключая её. В SDLPoP это
|
||||
чинит `FIX_RETREAT_WITHOUT_LEAVING_ROOM`, по умолчанию ВЫКЛЮЧЕННЫЙ, — мы
|
||||
портируем оригинал.
|
||||
|
||||
**Сторона стража проверена отдельно, расхождений нет.** `play_guard_frame`
|
||||
(seg000:0F48) ухода из комнаты не содержит вовсе (ни `leave_room`, ни
|
||||
`check_leave_below`), физика загейчена окном `Char.x ∈ [44, 211)`. Комнату
|
||||
страж меняет только через `follow_guard` (у нас `pop_guard_follow`) и
|
||||
`check_guard_fallout` (у нас `pop_guard_fallout`) — оба портированы.
|
||||
|
||||
**Проверка: покадровая сверка с SDLPoP.** Пользователь добавил в SDLPoP
|
||||
вывод в конце `add_kid_to_objtable` (seg008:1679); мы навесили такой же
|
||||
(`pop_dbg_kidobj`, см. ниже). Окно перехода совпало КАДР В КАДР:
|
||||
|
||||
| | SDLPoP | наша |
|
||||
|---|---|---|
|
||||
| | `tilepos=10 frame=161 col=-1` | `tilepos=10 frame=161 col=-1` |
|
||||
| | `tilepos=10 frame=160 col=-1` | `tilepos=10 frame=160 col=-1` |
|
||||
| | `tilepos=10 frame=157 col=-2` | `tilepos=10 frame=157 col=-2` |
|
||||
| | `tilepos=10 frame=158 col=-2` | `tilepos=10 frame=158 col=-2` |
|
||||
| **смена** | `tilepos=18 frame=169 col=9` | `tilepos=18 frame=169 col=9` |
|
||||
|
||||
Прогон уровня 1 комнаты 3 (1779 кадров): за весь бой комната сменилась
|
||||
ОДИН раз; 34 боевых кадра прошли за краем комнаты (`col<0`), самый длинный
|
||||
непрерывный прогон — 17 кадров, и комната при этом не переключалась — ровно
|
||||
как в трассе SDLPoP.
|
||||
|
||||
Прогон уровня 8, шов 24/18 (1433 кадра, 360 кадров с мечом): внутри боя
|
||||
**4 смены комнаты на 387 кадров** — то есть примерно раз в 6 секунд, и это
|
||||
настоящее выдавливание, а не мерцание. Все четыре пришлись на РАЗРЕШЁННЫЕ
|
||||
кадры (170, 164, 170, 165); ни одной смены на запрещённом кадре во всей
|
||||
трассе нет. Кид подолгу держался на `col=-1` и `col=10`, комната не
|
||||
переключалась.
|
||||
|
||||
**Инструмент (оставлен в дереве, выключен).** `pop_dbg_kidobj`
|
||||
(`roomtest_cold.c`) + резидентный буфер `pop_dbg_obj` (`pop_state.c`),
|
||||
включается `#define DBG_KIDOBJ 1` в `roomtest.c`. Считает то же, что
|
||||
`set_char_collision` + `set_objtile_at_char`. Вывод забирает брейкпоинт
|
||||
MAME на резидентном `pop_dbg_trap`; сборщик — `collect_kidobj.py`.
|
||||
|
||||
Грабли, стоившие времени:
|
||||
|
||||
- `#define DBG_KIDOBJ` сперва положили внутрь `#ifndef PROF_BORDER`, а
|
||||
`PROF_BORDER` задаётся ключом сборки — блок не выполнялся, трасса молча
|
||||
не включалась. Нужен свой `#ifndef`.
|
||||
- Знаковые значения через выражения отладчика MAME получить не вышло: `b@`
|
||||
связывается слабее `+`, и `b@ADDR + 128` читает ДРУГОЙ адрес; скобки не
|
||||
помогают. Зонд печатает сырые байты, перевод — в сборщике.
|
||||
- Кольцо `clog` не потребляющее, одинаковые строки от повторов не отличить —
|
||||
в строку добавлен `totalcycles`, по нему и склеиваются чанки.
|
||||
- Замерено: один зонд с коротким `printf` — **98 % полной скорости**
|
||||
эмуляции, играть можно (в отличие от четырёх зондов профилировщика).
|
||||
|
||||
**Осталось невыясненным.** Поле `cols` (`char_col_left..char_col_right`) у
|
||||
нас систематически на единицу меньше, чем в SDLPoP, и дважды дало `0..-1`,
|
||||
чего в оригинале не бывает. Все поля, которые считает ДВИЖОК (`tilepos`,
|
||||
`frame`, `act`, `col`, точка перехода), совпали полностью — расхождение
|
||||
только в величине, которую выводит сам отладочный помощник из ширины кадра
|
||||
`pop_cd[].fpw`. Подозрение на паддинг кадров в атласе
|
||||
(`png_strip_padding_tradeoff`) либо на мой пересчёт переднего края мимо
|
||||
боевого `char_x_forward_edge`. Проверять отдельно.
|
||||
|
||||
@@ -79,6 +79,14 @@ uint8_t pop_dbg_hits;
|
||||
|
||||
void pop_dbg_trap(void) { pop_dbg_hits++; }
|
||||
|
||||
/* ВРЕМЕННО: трасса kidobj — порт вывода, который пользователь добавил в
|
||||
* SDLPoP add_kid_to_objtable (seg008:1679), чтобы сверить поведение у шва
|
||||
* кадр в кадр. Буфер РЕЗИДЕНТНЫЙ, потому что читает его брейкпоинт MAME:
|
||||
* printf из действия точки берёт байты по фиксированному адресу. Поля —
|
||||
* ровно те же и в том же порядке, плюс наша комната девятым (у оригинала
|
||||
* смена комнаты видна по скачку tilepos, а нам удобнее явно). */
|
||||
int8_t pop_dbg_obj[9];
|
||||
|
||||
/* Зеркало уровня 4 — см. pop_state.h. */
|
||||
int8_t pop_jumped_mirror;
|
||||
|
||||
|
||||
@@ -96,3 +96,9 @@ void pop_dbg_kind(uint8_t k); void pop_dbg_m16(void);
|
||||
void pop_dbg_b1(void); void pop_dbg_b2(void); void pop_dbg_b3(void);
|
||||
void pop_dbg_b4(void); void pop_dbg_b5(void); void pop_dbg_b6(void);
|
||||
void pop_dbg_wh(uint16_t wh);
|
||||
|
||||
/* ВРЕМЕННО: трасса kidobj (см. pop_state.c). Порядок байт:
|
||||
* 0 obj_tilepos, 1 frame, 2 action, 3 curr_col, 4 char_col_left,
|
||||
* 5 char_col_right, 6 char_top_row, 7 char_bottom_row, 8 комната. */
|
||||
extern int8_t pop_dbg_obj[9];
|
||||
void pop_dbg_kidobj(void) __banked;
|
||||
|
||||
@@ -46,6 +46,14 @@ __sfr __at (0xFE) io_border;
|
||||
#ifndef PROF_BORDER
|
||||
#define PROF_BORDER 1
|
||||
#endif
|
||||
/* ВРЕМЕННО: покадровая трасса kidobj для сверки с SDLPoP (см.
|
||||
* pop_dbg_kidobj в roomtest_cold.c). 0 — выключить.
|
||||
* СВОЙ #ifndef, а не общий с PROF_BORDER: тот задаётся ключом сборки
|
||||
* (-DPROF_BORDER=1), и всё, что лежало внутри его блока, не определялось
|
||||
* вовсе — трасса молча не включалась. */
|
||||
#ifndef DBG_KIDOBJ
|
||||
#define DBG_KIDOBJ 0
|
||||
#endif
|
||||
#if PROF_BORDER
|
||||
#define PROF(n) io_border = (n)
|
||||
#else
|
||||
@@ -640,6 +648,12 @@ int main(void)
|
||||
if (!g_after && !(skip & 2)) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
|
||||
PROF(6); /* кадр Кида (тот же циан) */
|
||||
if (!(skip & 1)) pop_char_draw(POP_CH_KID); /* спрайт + брызги + клинок */
|
||||
#if DBG_KIDOBJ
|
||||
/* ВРЕМЕННО: трасса для сверки с SDLPoP кадр в кадр. Место — то
|
||||
* же, что у оригинала (конец add_kid_to_objtable), и ВНЕ skip:
|
||||
* пропуск перерисовки не должен рвать трассу. */
|
||||
pop_dbg_kidobj();
|
||||
#endif
|
||||
if (g_after && !(skip & 2)) { pop_char_draw(POP_CH_OPP); pop_char_fore(POP_CH_OPP); }
|
||||
/* Брызги урона рисует сама отрисовка персонажа (порядок objtable:
|
||||
* сразу после него и до клинка), а флаги снимаем здесь: соперника
|
||||
|
||||
@@ -499,6 +499,49 @@ static void objtile_at_char(int8_t *row, int8_t *col,
|
||||
*row = r; *col = c;
|
||||
}
|
||||
|
||||
/* ВРЕМЕННО: трасса kidobj — порт отладочного вывода, добавленного
|
||||
* пользователем в SDLPoP add_kid_to_objtable (seg008:1679):
|
||||
*
|
||||
* DBG kidobj tilepos=%d frame=%d act=%d col=%d cols=%d..%d rows=%d..%d
|
||||
*
|
||||
* Считаем ровно то же, что set_char_collision (seg006:0723) +
|
||||
* set_objtile_at_char (seg006:13F3): габарит НЕутоньшённый (THIN правит
|
||||
* только *_coll, а колонки берутся по «толстой» границе), колонки
|
||||
* клампятся в 0..9, нижний ряд −1 читается как 3. Звать ПОСЛЕ отрисовки
|
||||
* Кида: ширину/высоту кадра (fpw/fph) заполняет она.
|
||||
*
|
||||
* Вывод забирает брейкпоинт MAME на резидентном pop_dbg_trap — сама
|
||||
* функция банковая, её адрес в W3 неоднозначен. */
|
||||
void pop_dbg_kidobj(void) __banked
|
||||
{
|
||||
int8_t dx = kid_cur_dx(), dy = kid_cur_dy();
|
||||
uint8_t fl = kid_cur_flags();
|
||||
uint16_t w = pop_cd[POP_CH_KID].fpw;
|
||||
uint16_t h = pop_cd[POP_CH_KID].fph;
|
||||
int xl = char_x_left_of(Kid.x, Kid.direction, dx, fl, w);
|
||||
int xr = xl + (int)((w + 1) >> 1); /* + char_width_half */
|
||||
int oy = (int)Kid.y + dy;
|
||||
int ty = oy - (int)h + 1;
|
||||
int8_t r, c, tr, br, cl, cr;
|
||||
objtile_at_char(&r, &c, &Kid, dx, dy, fl, w);
|
||||
br = pop_y_to_row((int16_t)oy);
|
||||
if (br == -1) br = 3;
|
||||
if (ty >= 192) ty = 0;
|
||||
tr = pop_y_to_row((int16_t)ty);
|
||||
cl = tile_div_mod(xl); if (cl < 0) cl = 0;
|
||||
cr = tile_div_mod(xr); if (cr > 9) cr = 9;
|
||||
pop_dbg_obj[0] = (int8_t)(r * 10 + c);
|
||||
pop_dbg_obj[1] = (int8_t)Kid.frame;
|
||||
pop_dbg_obj[2] = (int8_t)Kid.action;
|
||||
pop_dbg_obj[3] = Kid.curr_col;
|
||||
pop_dbg_obj[4] = cl;
|
||||
pop_dbg_obj[5] = cr;
|
||||
pop_dbg_obj[6] = tr;
|
||||
pop_dbg_obj[7] = br;
|
||||
pop_dbg_obj[8] = (int8_t)cur_room;
|
||||
pop_dbg_trap();
|
||||
}
|
||||
|
||||
/* 1 = страж рисуется ПОСЛЕ Кида (поверх). __banked: зовётся раз в кадр,
|
||||
* то есть один трамплин — а 500 Б кода в резиденте дороже. */
|
||||
uint8_t guard_over_kid(void) __banked
|
||||
|
||||
Reference in New Issue
Block a user