diff --git a/applications/PoP/docs/perf_l11_room15.md b/applications/PoP/docs/perf_l11_room15.md index b2bc1e7..5faa46a 100644 --- a/applications/PoP/docs/perf_l11_room15.md +++ b/applications/PoP/docs/perf_l11_room15.md @@ -110,6 +110,9 @@ heal 32×64 ≈ 33 700, значит на один `draw_tile` уходит ≈ | 2026-08-19 | базовый замер (`09f32ce`) | 801 768 | 293 238 | 320 916 | 187 758 | | 2026-08-19 | **P5**: гейты холостого хода в `pop_loose_tick` | **767 928** | 285 864 | 294 384 | 187 764 | | | | −33 840 | −7 374 | −26 532 | +6 | +| 2026-08-19 | зонды для замера P2/P6 (временные) | 768 684 | 286 518 | 294 510 | 187 761 | + +Оснастка P2/P6 стоит 756 тактов на кадр — замеры до и после сопоставимы. Циан не изменился (+6 тактов — шум), и это ожидаемо: `loose_tick` целиком лежит в зелёной. Синяя просела на 7 374 без прямой причины в правке — diff --git a/applications/PoP/docs/perf_registry.md b/applications/PoP/docs/perf_registry.md index bf2e4b7..396fdd0 100644 --- a/applications/PoP/docs/perf_registry.md +++ b/applications/PoP/docs/perf_registry.md @@ -85,15 +85,45 @@ Новое, найдено 2026-08-19. -### P2. Логика двух Char — 152 190 тактов БЕЗ единого блита [гипотеза: −40 000 … −70 000] +### P2. Синяя фаза разложена [ЗАМЕР 2026-08-19] — гипотеза не подтвердилась -`m1→C`: `kid_tick`, `pop_phys_tick`, `pop_guard_tick`, `pop_guard_phys_tick`, -боёвка, `pop_check_can_guard_see_kid`. Графики там нет вообще, а стоит это -19 % работы кадра — столько же, сколько вся отрисовка обоих персонажей. +Замер зондами внутрь обеих половин синей (286 518 тактов): -**Сначала замер** (зонды внутрь синей фазы, нужна пересборка), потом решение. -Кандидаты по прошлым находкам: `sdcc_z80_stack_locals_hot_loop` (локали в -IX-фрейме), деления в луче видимости, банковые трамплины на каждый шаг. +| участок | такты | доля работы | +|---|---:|---:| +| ввод + читы | 14 154 | 1,8 % | +| три спецсобытия уровней (skel / mouse / killed_shadow) | **1 650** | 0,2 % | +| `pop_frame_timers` + **луч видимости стража** | **37 032** | 4,8 % | +| `pop_ctrl_tick` | 18 648 | 2,4 % | +| `skip_mask` + **heal двух Char** | **67 734** | 8,8 % | +| `mirror_heal` + `fore_heal` | 2 040 | 0,3 % | +| `kid_tick` (play_seq) | 10 098 | 1,3 % | +| **`pop_phys_tick`** (физика Кида) | **61 266** | 8,0 % | +| `pop_guard_tick` (логика стража) | 19 932 | 2,6 % | +| **`pop_guard_phys_tick`** (физика стража) | **43 980** | 5,7 % | +| боёвка (`sword_hurting` / `sword_hurt` / `delta_hp`) | 9 558 | 1,2 % | +| `guard_fallout` + уход из комнаты | 366 | — | + +**Что оказалось не так, как ждали.** Я предполагал, что дорогие тут +банковые трамплины на спецсобытиях (по аналогии с `pop_clip_char_top`, +8 892 такта за трамплин ради одной проверки). Замер это отверг: три +спецсобытия уровней вместе стоят **1 650** — они гейтятся внутри и на +уровне 11 честно выходят сразу. + +**Настоящие статьи — три:** + +1. **Физика двух Char — 105 246** (61 266 + 43 980), при том что оба + персонажа СТОЯТ и кадр позы не меняется. Это 13,7 % работы кадра и + самая крупная статья синей. Нужен ещё один уровень разбора — внутрь + `pop_phys_tick` (позиция **P2a**, отдельным заходом). +2. **Луч видимости стража — до 37 032** (вместе с `pop_frame_timers`, но тот + заведомо копеечный: три счётчика). Считается КАЖДЫЙ кадр, хотя ни Кид, + ни страж не сдвинулись. Кандидат на гейт «пересчитывать только при + смене позиции или комнаты любого из двоих» — позиция **P2b**, + ожидание −30 000, риск низкий. +3. **heal двух Char — 67 734.** Отдельной правки не требует: он платится + ровно потому, что `skip_mask` никого не пропускает, и уйдёт вместе с + P1/P3. Новое, найдено 2026-08-19. @@ -156,11 +186,28 @@ heal 141 048 + отрисовка 148 566 = 290 000 тактов (36 % рабо (фаза восстановлена входом в комнату), которое не покрывал никто. Мутационная проверка: со снятым взводом тест падает. -### P6. Цикл `pop_process_trobs` — 59 100 без блитов пламени [гипотеза: −20 000 … −40 000] +### P6. `pop_process_trobs` разложен [ЗАМЕР 2026-08-19] — 89 784 -Префетч кодов тайлов делает `pop_level_access_begin/end` (маппинг окна 0) -каждый кадр; дальше switch по всем trob'ам комнаты. Надо померить, сколько -там trob'ов и куда уходит время. +| участок | такты | +|---|---:| +| вход + префетч кодов тайлов (маппинг окна 0) | **11 058** | +| цикл: два `pop_torch_draw` | ~36 000 | +| цикл: обход самих trob'ов | ~43 000 | + +Цена одного `pop_pot_b` (пламя факела) измерена отдельно, брейкпоинтами на +резидентных адресах: **17 346 тактов**, и это ЕДИНСТВЕННАЯ группа в +распределении — то есть `pop_pot_b` в кадре зовут только два факела. При +канвасе пламени 16×18 сами пиксели там 1 716, то есть **10 % цены**; всё +остальное — накладные (см. §1) плюс ~6 900 сверх `pop_blit_b` на самом +`pop_pot_b`. + +Направления: + +- **P6a**: `pop_trob_modif(room)` зовётся банковым вызовом на КАЖДЫЙ trob + внутри цикла, хотя комната у них одна и та же — вынести наружу; +- **P6b**: префетч 11 058 маппит окно 0 каждый кадр, а коды тайлов trob'ов + меняются редко — кэшировать с инвалидацией по смене тайла/комнаты; +- **P6c**: цена факела — это цена блита, то есть позиция P4. Новое, найдено 2026-08-19. @@ -230,16 +277,41 @@ BUG-LATTICE-DOORTOP, BUG-SEAM-WEDGE-1) — только отдельным за | # | что | статус | факт | |---|---|---|---| | P5 | `loose_tick`: гейты холостого хода | ✅ **сделано** 2026-08-19 | −33 840 на кадре (ждали −28 000) | -| P2 | замер логики двух Char | ⬜ следующее | нужна пересборка с зондами | -| P6 | замер цикла `process_trobs` | ⬜ вместе с P2 | та же пересборка | -| P1 | чомпер под факелом: только слой anim | ⬜ | ждём −160 000 | +| P2 | замер синей фазы | ✅ замерено 2026-08-19 | гипотеза «трамплины» отвергнута | +| P6 | замер `process_trobs` | ✅ замерено 2026-08-19 | 11 058 префетч + ~43 000 цикл | +| P1 | чомпер под факелом: только слой anim | ⬜ **следующее** | ждём −160 000 | +| P2b | гейт луча видимости стража | ⬜ | ждём −30 000, риск низкий | | P4 | накладные блита (4 правки) | ⬜ | ждём −28 000 | | P11 | мелочи с известной ценой | ⬜ | ждём −26 000 | | P10 | футпринт персонажа из физики | ⬜ | ждём −23 000 | +| P6a/P6b | `trob_modif` из цикла, кэш префетча | ⬜ | ждём −20 000 | +| P2a | разбор `pop_phys_tick` (105 246 на двоих) | ⬜ | цена известна, причина нет | | P7 | раскол `draw_tile` (G5) | ⬜ | для 13/23, не для 11/15 | | P8/P9 | HEAL-WIDTH + G8 | ⬜ обязательная | для сцен с плитами | | P12 | снять оснастку | ⬜ ПОСЛЕДНЕЙ | без неё не мерить | +### Достижим ли период 3 растра — арифметика после замеров + +Работа сейчас **767 928**, цель **430 000**, снять надо **338 000**. Что +известно и посчитано: + +| источник | ожидание | +|---|---:| +| P1 (чомпер: только слой anim) | −160 000 | +| P3 (Кид перестанет будиться, следствие P1) | −70 000 | +| P2b (гейт луча видимости) | −30 000 | +| P4 (накладные блита) | −28 000 | +| P11 (мелочи) | −26 000 | +| P10 (футпринт из физики) | −23 000 | +| P6a+P6b | −20 000 | +| **сумма** | **−357 000** | + +То есть цель достижима, но **впритык и только если сделать ВСЁ перечисленное**, +причём половина суммы держится на одной позиции (P1). Запаса нет, и любая +позиция, давшая меньше ожидаемого, возвращает нас к разбору `pop_phys_tick` +(P2a) — 105 246 тактов на физику двух НЕПОДВИЖНЫХ персонажей, где цена +измерена, а причина ещё не найдена. + **Текущее состояние бюджета 11/15:** | | работа | синяя | зелёная | циан | период | diff --git a/applications/PoP/roomtest/pop_trob.c b/applications/PoP/roomtest/pop_trob.c index ad36bc7..8585e08 100644 --- a/applications/PoP/roomtest/pop_trob.c +++ b/applications/PoP/roomtest/pop_trob.c @@ -633,6 +633,8 @@ void pop_process_trobs(uint8_t cur_room) __banked pop_level_access_end(); } + pop_dbg_m8(); /* ЗАМЕР: префетч кодов тайлов сделан */ + for (i = 0; i < trobs_count; i++) { uint8_t room = trobs[i].room; uint8_t tp = trobs[i].tilepos; diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 721aa2a..aead726 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -361,35 +361,45 @@ int main(void) back = dbuf ? (uint8_t)(gfx_get_visible_page() ^ 1) : 0; gfx_set_draw_page(back); } + pop_dbg_m13(); /* ЗАМЕР: ввод и читы разобраны */ pop_check_skel(); /* спецсобытие ур.3: скелет встаёт */ pop_check_mouse(); /* спецсобытие ур.8: мышь жмёт кнопку */ pop_check_killed_shadow(); /* спецсобытие ур.12: смерть тени = смерть Кида */ + pop_dbg_b6(); /* ЗАМЕР: три спецсобытия уровней сделаны */ pop_frame_timers(); /* timers (seg003:0735): вспышка слияния */ pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */ + pop_dbg_m14(); /* ЗАМЕР: спецсобытия + луч видимости */ pop_ctrl_tick(); /* ввод -> control(): смена seq */ /* Кто в этом кадре не изменился с прошлой отрисовки ЭТОЙ страницы — * тот на ней уже нарисован правильно: ни стирать, ни рисовать заново * не надо (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);