Замер цианной фазы: крупного лишнего в отрисовке персонажа нет

Циан 188 004 не двигался ни от P1, ни от P5, ни от P2b — разложил его
зондами.

Хорошая новость: Кид УЖЕ пропускается (204 такта на pop_char_draw), то
есть надежда P3 сбылась после P1 — метка от чомпера до него больше не
дотягивается.  Страж же перерисовывается каждый кадр честно: пламя
правого факела (0,7) рисуется в ячейке (0,8), где он стоит, и реально
накрывает ему голову (пламя занимает y 5..22, страж 12..62).

Отрисовка стража — 148 302:

  pop_char_fore (2 трамплина в банк 2 + обход тайлов)  62 778   42 %
  клинок (sword_draw + overlay_add + clip_add)         27 522   19 %
  блит спрайта + clip_char_right                       20 982   14 %
  загрузка кадра и геометрия                           12 696    9 %
  pop_clip_char_top (трамплин банк 4 -> банк 3)         8 658    6 %
  снимок прямоугольника + cd_clip_add                   7 890    5 %
  gfx_w0_unmap + cd_sig_make                            4 968    3 %
  вход + cd_heal                                        2 946    2 %

Единственная явно лишняя статья — трамплин clip_char_top, и снять его
непросто: функции нужны get_tile и таблицы деления из банка 3, перенос в
резидент вернёт тот же трамплин внутрь.  Остальное — работа, которую
персонаж действительно делает.

Зонды переставлены с уже закрытых замеров (loose_tick, физика) внутрь
pop_cdraw; оснастка снимается позицией P12, когда оптимизация закончится.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 12:53:49 +03:00
parent 068e21b56a
commit 6faf81016a
4 changed files with 50 additions and 11 deletions
+38
View File
@@ -405,6 +405,44 @@ BUG-LATTICE-DOORTOP, BUG-SEAM-WEDGE-1) — только отдельным за
---
## 5б. Цианная фаза разложена [ЗАМЕР 2026-08-19]
Фаза 188 004 тактов, и она НЕ менялась ни от P1, ни от P5, ни от P2b.
| участок | такты |
|---|---:|
| `check_mirror` | 3 198 |
| `loose_mob_draw` + `guard_over_kid` + `skip_mask` | **34 374** |
| **`pop_char_draw(KID)`** | **204** |
| **соперник: `char_draw` + `char_fore`** | **145 896** |
| `fore_needed` + `hp_draw` | 2 598 |
| `char_fore(KID)` + борта | 1 758 |
**Кид уже пропускается** — 204 такта, то есть надежда P3 всё-таки сбылась
после P1: метка от чомпера до него больше не дотягивается. А страж
перерисовывается каждый кадр, и это ЧЕСТНО: пламя правого факела (0,7)
рисуется в ячейке (0,8), где он стоит, и реально накрывает ему голову
(пламя занимает y 5..22, страж 12..62).
Отрисовка стража (148 302) по частям:
| участок | такты | доля |
|---|---:|---:|
| **`pop_char_fore`** (два трамплина в банк 2 + обход тайлов) | **62 778** | 42 % |
| клинок: `pop_sword_draw` + `cd_overlay_add` + `cd_clip_add` | 27 522 | 19 % |
| блит спрайта + `clip_char_right` | 20 982 | 14 % |
| загрузка кадра и геометрия | 12 696 | 9 % |
| **`pop_clip_char_top`** (трамплин банк 4 → банк 3) | 8 658 | 6 % |
| снимок прямоугольника + `cd_clip_add` | 7 890 | 5 % |
| `gfx_w0_unmap` + `cd_sig_make` | 4 968 | 3 % |
| вход + `cd_heal` | 2 946 | 2 % |
**Вывод: крупного лишнего здесь нет.** Единственная явно лишняя статья —
трамплин `clip_char_top` (8 658), и убрать его непросто: функции нужны
`get_tile` и таблицы деления из банка 3, а перенос в резидент вернёт тот же
трамплин внутрь. Всё остальное — работа, которую персонаж действительно
делает: рисует себя, клинок и передний слой поверх себя.
## 6. Иерархия референсов (уточнена 2026-08-19)
Сравнение трёх реализаций луча видимости показало, что источники не
+9
View File
@@ -23,6 +23,7 @@
#include "_pop_kdraw.h"
#include "pop_vflip.h" /* зеркальные страницы атласов (зелье инверсии) */ /* меч + блит по id — сосед по банку 4 */ /* атласы/кадр Кида, pop_sword_draw, окна Char */
#include "pop_cdraw.h"
#include "pop_state.h" /* ВРЕМЕННО: зонды pop_dbg_b* */
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char, pop_fore_set_clip */
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида; clip_char */
@@ -503,6 +504,7 @@ void pop_char_draw(uint8_t who) __banked
* сдвинул. Здесь прошлый кадр уже точно надо стереть. Если heal в этом
* кадре был, вызов бесплатен: valid/ovalid уже сняты. */
cd_heal(who);
pop_dbg_b1(); /* ЗАМЕР: cd_heal сделан */
s->cw = 0; /* окно fore-клипа: пусто, пока не нарисовали */
s->fpw = 0; /* габарит кадра: нечего перекрывать */
@@ -633,7 +635,9 @@ void pop_char_draw(uint8_t who) __banked
* так что «убрать последние bcut строк вывода» = «не рисовать
* первые bcut строк оригинала», а это ровно то, что просит
* clip_char. */
pop_dbg_b2(); /* ЗАМЕР: кадр загружен, геометрия готова */
ct = pop_clip_char_top(fp_x, obj_y, w, h);
pop_dbg_b3(); /* ЗАМЕР: clip_char_top (трамплин банк 4->3) */
if (!pop_upside) {
if (ct > top) {
skip = ct - top;
@@ -691,6 +695,7 @@ void pop_char_draw(uint8_t who) __banked
(uint8_t)(vis_w == w ? 0 : vis_w));
} else rows = 0;
gfx_set_bank(GFX_BANK_NORMAL);
pop_dbg_b4(); /* ЗАМЕР: спрайт выведен */
/* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над
* срезом (мусор/дыра в кладке на второй странице дабл-буфера). */
s->x[dp] = bx + lskip; s->y[dp] = top;
@@ -700,6 +705,7 @@ void pop_char_draw(uint8_t who) __banked
s->fpw = w; s->fph = h; /* габарит КАДРА (не обрезанный): */
/* по нему считается футпринт */
}
pop_dbg_m15(); /* ЗАМЕР: снимок прямоугольника сделан */
if (hurt) cd_splash(s, who, pages, fp_x, obj_y);
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
* персонажа — со своим прямоугольником heal. */
@@ -710,6 +716,7 @@ void pop_char_draw(uint8_t who) __banked
cd_clip_add(s, sx, sy, sw, sh);
}
}
pop_dbg_m16(); /* ЗАМЕР: splash + клинок сделаны */
gfx_w0_unmap();
/* Что именно нарисовано на ЭТОЙ странице — снимок для пропуска
* следующих кадров (DRAW-COST). Только если кадр РЕАЛЬНО рисовали:
@@ -722,6 +729,7 @@ void pop_char_draw(uint8_t who) __banked
* между ними (BUG-CHAR-STALE-PAGE). Без снимка слот просто не будет
* «тихим», и следующий кадр начнётся с heal — старое сотрётся. */
if (w && h) cd_sig_make(who, &cd_sig[who][dp]);
pop_dbg_b5(); /* ЗАМЕР: pop_char_draw целиком */
}
void pop_char_fore(uint8_t who) __banked
@@ -738,6 +746,7 @@ void pop_char_fore(uint8_t who) __banked
if (s->fpw)
pop_fore_over_char(who == POP_CH_KID ? &Kid : &Guard,
s->fpx, s->fpy, s->fpw, s->fph);
pop_dbg_b6(); /* ЗАМЕР: pop_char_fore целиком */
}
/* ---- ОТРАЖЕНИЕ В ЗЕРКАЛЕ (check_mirror, seg003:0798) ---------------- *
-3
View File
@@ -2776,11 +2776,8 @@ void pop_loose_tick(void) __banked
}
loose_any = found;
}
pop_dbg_m10(); /* ЗАМЕР: оба цикла по тайлам пройдены */
pop_loose_mob_tick(); /* продвинуть+нарисовать падающий кусок (после тайлов) */
pop_dbg_m11(); /* ЗАМЕР: pop_loose_mob_tick сделан */
check_loose_fall_on_kid(); /* seg007:1192 — плита падает Киду на голову */
pop_dbg_m12(); /* ЗАМЕР: check_loose_fall_on_kid сделан */
/* Отложенная допечка ПРОШЛОГО приземления на вторую страницу — ПЕРЕД
* разбором нового: иначе при двух плитах, севших подряд, land_bake
* затирался новым и на одной странице оставался старый пол. */
+3 -8
View File
@@ -365,7 +365,6 @@ int main(void)
pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */
pop_check_mouse(); /* спецсобытие ур.8: мышь жмёт кнопку */
pop_check_killed_shadow(); /* спецсобытие ур.12: смерть тени = смерть Кида */
pop_dbg_b6(); /* ЗАМЕР: три спецсобытия уровней сделаны */
pop_frame_timers(); /* timers (seg003:0735): вспышка слияния */
pop_dbg_m9(); /* ЗАМЕР: pop_frame_timers сделан */
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
@@ -375,32 +374,25 @@ int main(void)
* тот на ней уже нарисован правильно: ни стирать, ни рисовать заново
* не надо (DRAW-COST, контракт в pop_cdraw.h). Решение
* перепроверяется перед самой отрисовкой — тик ещё впереди. */
pop_dbg_m15(); /* ЗАМЕР: pop_ctrl_tick сделан */
skip = pop_char_skip_mask();
if (!(skip & 1)) pop_char_heal(POP_CH_KID); /* стереть прошлый кадр ЭТОЙ страницы */
if (!(skip & 2)) pop_char_heal(POP_CH_OPP); /* и прошлый кадр соперника */
/* Отражение в зеркале — свой heal: у него нет слота pop_cd и в
* skip-маске он не участвует (рисуется/стирается безусловно, живёт
* только пока Кид стоит на тайле зеркала). */
pop_dbg_m16(); /* ЗАМЕР: heal обоих персонажей сделан */
pop_mirror_heal();
pop_fore_heal(); /* + следы оверлея (шире прямоугольника Kid) */
PROF(2); /* логика (тот же синий) */
pop_dbg_m1(); /* ЗАМЕР: начало логики */
kid_tick(); /* play_seq: следующий кадр */
pop_dbg_b1(); /* ЗАМЕР: kid_tick сделан */
pop_phys_tick(); /* K3: падение/приземление/стена */
pop_dbg_b2(); /* ЗАМЕР: pop_phys_tick сделан */
pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */
pop_dbg_b3(); /* ЗАМЕР: pop_guard_tick сделан */
pop_guard_phys_tick(); /* и его физика — та же, что у Кида (play_guard_frame) */
/* Боёвка, порядок как в play_frame (seg000:872): сначала пометить
* попадания с обеих сторон, потом применить, потом свести HP. */
pop_dbg_b4(); /* ЗАМЕР: pop_guard_phys_tick сделан */
pop_check_sword_hurting();
pop_check_sword_hurt();
pop_do_delta_hp();
pop_dbg_b5(); /* ЗАМЕР: боёвка сделана */
pop_guard_fallout(); /* страж провалился ниже комнаты — убрать (seg002:241) */
if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
@@ -647,6 +639,7 @@ int main(void)
* фон кадра (перепечатки тайлов, шов) обязан быть готов ДО любого
* спрайта, иначе перепечатка ложится поверх уже нарисованного куска —
* так окно задней стены и передняя грань пола оказывались на плите. */
pop_dbg_m10(); /* ЗАМЕР: check_mirror сделан */
pop_loose_mob_draw();
{ /* Кто позже — тот поверх; порядок задаёт обход тайлов, а не роль
* персонажа (см. guard_over_kid). Отрисовка одна на всех Char
@@ -659,6 +652,7 @@ 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); /* спрайт + брызги + клинок */
pop_dbg_m11(); /* ЗАМЕР: Кид нарисован */
#if DBG_KIDOBJ
/* ВРЕМЕННО: трасса для сверки с SDLPoP кадр в кадр. Место — то
* же, что у оригинала (конец add_kid_to_objtable), и ВНЕ skip:
@@ -679,6 +673,7 @@ int main(void)
/* ПЕРЕДНИЙ СЛОЙ по пометкам — последним в кадре, как foretable у
* оригинала (draw_tables, seg008:1373). Пометки ставят сами
* объекты вокруг себя (pop_redraw.h). */
pop_dbg_m12(); /* ЗАМЕР: соперник + mob поверх сделаны */
pop_fore_needed();
pop_hp_draw(); /* полоса HP — только при изменении */
PROF(6); /* fore поверх Kid + борта */