31b82661eb
Порт PoP переехал в applications/SprPoP — приложение, которое собирается само: код, оригинальные данные, конверторы ресурсов и сборка внутри одной папки. Наружу знает единственный путь — корень тулчейна (SPRINTER_ROOT, по умолчанию ../..). applications/PoP/roomtest ЗАМОРОЖЕНА и остаётся архивом закрытых задач, багов и исполненных планов. Скопировано из applications/PoP/roomtest@4b74478. Перенос проверен побайтово: собранный sprpop.exe совпал с roomtest.exe того же коммита, все 39 дисковых ресурсов и все 16 генерируемых заголовков — тоже, host- тесты зелёные (15/15). Раскладка: src/ рукописный C (roomtest.c -> sprpop.c) gen/ генерируемые заголовки, в репозитории assets/orig/ оригинальные данные игры, вне репозитория (копирайт) assets/packed/ то, что ложится на диск, в раскладке диска tools/ конверторы; все пути — в одном tools/paths.py build/ выход: exe, каталоги ресурсов, hdd/, промежуточные atl/ Сборка ресурсов: assets/packed и gen — версионируемые ВХОДЫ, а не то, что пересчитывается каждым make. Автоматика построена на ОТСУТСТВИИ файла, а не на таймстемпах: git не хранит времена, и в свежем клоне сравнение по времени превращалось бы в лотерею. Недостающий ресурс или заголовок чинится сам, рекурсивным вызовом в ветку генерации. Музыка собирается из любого из четырёх наборов записей (make music-mp3, music-mt32, ...); набор входит в имя stamp'а, поэтому смена набора сама делает музыку устаревшей. Длины реплик больше не захардкожены: упаковщик печатает их в gen/pop_music_ticks.h, и шкала сцены выражена через них — иначе mt32 (реплики на 6% длиннее) молча ломал катсцену. Тулчейн: в app.mk два обратносовместимых крючка (SRC_DIR/BUILD_DIR), HDD_IMG стал ?=; команда сборки roomtest не изменилась. Корневой make host-tests переключён на SprPoP. Подгонка тайминга катсцены с принцессой (PV_MAGIC_LEAD): сцена render-bound и идёт ~49 тиков/с вместо 60, из-за чего кода реплики приходила раньше молнии. Это обход, а не лечение; разбор с замерами — docs/BUGS_OPEN.md, записи SND-PACE-DEAD, PV-RENDER-BOUND, MUS-LEFT-TEAR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
328 lines
23 KiB
Markdown
328 lines
23 KiB
Markdown
# ЗЕЛЁНАЯ фаза (слой фона) — анализ и оптимизация
|
||
|
||
Рабочий документ: живёт между сессиями. Внизу **журнал правок** — каждая
|
||
запись с замером до/после. Сцена, рецепт воспроизведения и зонды —
|
||
[`perf_l13_room23.md`](perf_l13_room23.md); там же ответ про 8-битные
|
||
габариты. Парная фаза — [`perf_cyan_phase.md`](perf_cyan_phase.md) (её цель
|
||
достигнута).
|
||
|
||
Границы фазы в `sprpop.c`: от `PROF(4)` (строка 450) до `PROF(6)`
|
||
(строка 620). Содержимое: `pop_loose_tick`, `pop_process_trobs`,
|
||
`pop_redraw_needed`, шов, смена уровня, сигналы провалов, вспышка.
|
||
|
||
**Бюджет: ≤ 400 000 тактов (растровый кадр = 430 000).**
|
||
|
||
| состояние | было (`c312e4a`) | стало (`af189a1`) |
|
||
|---|---:|---:|
|
||
| покой в комнате 23 | 35 760 | 35 760 |
|
||
| дрожат 6 плит-потолков | 366 000 | 335 400 |
|
||
| **пик каскада** | **805 000** | **546 900** |
|
||
|
||
**ЦЕЛЬ ФАЗЫ НЕ ДОСТИГНУТА: 546 900 против 400 000 (1,37×).** Что осталось
|
||
сделать и почему это именно раскол `draw_tile` — §3.
|
||
|
||
---
|
||
|
||
## 1. Раскладка ПОСЛЕ правок (замер `af189a1`)
|
||
|
||
Пик — кадры 24-28 (посадки плит), больше не кадры провалов.
|
||
|
||
| участок | покой | дрожь | пик |
|
||
|---|---:|---:|---:|
|
||
| `pop_loose_tick` | 36 234 | 36 234 | **156 762** |
|
||
| `pop_process_trobs` | 1 050 | 1 050 | 1 050 |
|
||
| **`pop_redraw_needed`** | 924 | 288 800 | **380 568** |
|
||
| хвост (шов, смена уровня, сигналы, вспышка) | 9 336 | 9 336 | 10 776 |
|
||
|
||
`pop_loose_tick` изнутри на пике: два цикла по тайлам 9 852,
|
||
**`pop_loose_mob_tick` 154 074** (heal шести летящих кусков), остальное мелочь.
|
||
|
||
### Цена одной перерисовки: было → стало
|
||
|
||
| вид | было | стало | чем |
|
||
|---|---:|---:|---|
|
||
| `RDA_CEIL` — дрожащая плита-потолок | 48 785 | **43 536** | G2 + G3 |
|
||
| `RDA_CEIL_GONE` — запечь колодец | 251 335 | **138 318** | **G1** (клип полосы) + G2 + G3 |
|
||
| `RD_FLOOR` — щебень на месте посадки | 198 805 | **179 914** | G2 + G3 + снятая двойная пометка |
|
||
|
||
Штук за кадр: `RDA_CEIL` до 6, `RDA_CEIL_GONE` до 2, `RD_FLOOR` до 2.
|
||
|
||
### Детальный профиль `RD_FLOOR` (179 914) — главная оставшаяся статья
|
||
|
||
Снят зондами по каждому блиту (`pop_dbg_b1/b5`) и по рамкам
|
||
(`pop_bar_black`, `pop_cd_batch_begin/end`):
|
||
|
||
| участок | такты |
|
||
|---|---:|
|
||
| вход `pop_floor_bake` + `gfx_set_bank` | 2 382 |
|
||
| `pop_bar_black` 60×39 (включая `pop_cd_touch` 4 502) | ~15 500 |
|
||
| **контекст `draw_tile` #1** (5 чтений тайлов, `63*row`, индексация таблицы) | **13 584** |
|
||
| 4 блита тайла #1 | 50 718 |
|
||
| **диспетчер между блитами #1** (все `if (code == …)`) | **11 388** |
|
||
| **контекст `draw_tile` #2** | **13 584** |
|
||
| 4 блита тайла #2 | ~54 000 |
|
||
| **диспетчер между блитами #2** | **11 388** |
|
||
| хвост | 6 474 |
|
||
|
||
Итого: **105 500 — сами блиты (реальные пиксели), 74 400 — накладные**, из
|
||
которых 27 168 контекст двух `draw_tile` и 22 776 их диспетчер.
|
||
|
||
---
|
||
|
||
## 2. Что сделано (с чем сравнивать)
|
||
|
||
### G1. Окно клипа для точечной перерисовки — −90 000
|
||
|
||
`pop_t_win_set(x, ytop, w, h)` / `pop_t_win_clear()` в `pop_tile.c`: ставит
|
||
уже существующее окно `pop_t_fclip_*` на прямоугольник, который перерисовка
|
||
восстанавливает. Работает в обе стороны — и предфильтр `pop_blit_b`
|
||
отсеивает куски мимо окна ДО `atlas_image`/`gfx_w0_map`, и `blit_b_clip`
|
||
режет остальные по нему.
|
||
|
||
Где сработало: `pop_ceil_bake_empty` — куски ряда 0 высотой 63 px рисовались
|
||
целиком, хотя восстановить надо девять строк полосы. **Блит 21 447 → 7 619**,
|
||
вся перерисовка 251 335 → 138 318.
|
||
|
||
Где НЕ сработало — см. §4, отрицательные результаты.
|
||
|
||
Побочно: пока окно стоит, `pop_blit_b` не ставит пометку «фон трогали»
|
||
(признак fore-прохода), поэтому вызывающий обязан пометить прямоугольник сам.
|
||
В `pop_ceil_shake_draw` добавлен явный `pop_cd_touch` на область heal'а; в
|
||
`pop_ceil_bake_empty` и `pop_floor_bake` метит `pop_bar_black`, а лишний
|
||
второй вызов на ту же область снят.
|
||
|
||
### G2. Контекст тайла — file-scope, а не локали `draw_tile` — −55 000
|
||
|
||
Порт `load_curr_and_left_tile` (seg008:0339): у оригинала это
|
||
`curr_tile`/`curr_modifier`/`draw_xh`/`draw_main_y`/`draw_bottom_y` —
|
||
переменные модуля, а не локали.
|
||
|
||
Причина в кодогене: в `draw_tile` **57 вызовов**, и каждое живое через вызов
|
||
значение SDCC спиливал в стековый кадр — 26 байт кадра и **513 обращений
|
||
`-N(ix)`** (при ~46 замеренных тактах на обращение это ~23 600, что и
|
||
намерено). Стало **33 обращения**, кадра нет, банк 7 −703 Б.
|
||
|
||
### G3. `blit_b_clip` — байтовый габарит + file-scope
|
||
|
||
Два шага, и важен порядок наблюдений:
|
||
|
||
1. **Байтового габарита ОДНОГО НЕ ХВАТИЛО.** `sx/sy/dw/dh` → `uint8_t`
|
||
(корректно: кадры атласов ≤ 56×63) дало 211 → 173 обращения, а
|
||
22-байтовый кадр остался: значений, живых через шесть вызовов ядер libbgi,
|
||
всё равно больше, чем регистров у Z80.
|
||
2. **Решило вынесение из локалей** (`bc_*`): 51 обращение, кадр 22 → 12 Б.
|
||
|
||
Клипованный блит 14 088 → 11 848. Заодно `blit_b_oversize` больше не ходит
|
||
через `blit_b_clip` (там теперь байтовый габарит) — рисует напрямую
|
||
`gfx_blit_part`; это путь под полноэкранные подложки интро/финала.
|
||
|
||
### G4. Мелочи
|
||
|
||
- `pop_blit_b`: аргументы в file-scope (третий и дальше SDCC передаёт стеком,
|
||
каждое чтение шло через `-N(ix)`) — 76 → 11 обращений.
|
||
- `pop_loose_mob_tick`: пометки всех кусков ОДНИМ пакетом
|
||
(`pop_cd_batch_begin/end`) — было по 4 502 такта на кусок.
|
||
176 772 → 168 600.
|
||
- Коридор heal куска — по фактической высоте СОБРАННОГО композита (было 24
|
||
строки константой, стало 20). 168 600 → 156 762.
|
||
|
||
---
|
||
|
||
## 3. Что осталось: раскол `draw_tile` (позиция G5)
|
||
|
||
**Оставшийся разрыв: −147 000.** Он весь в двух местах.
|
||
|
||
### G5. Расколоть `draw_tile` на узкие части, как в оригинале
|
||
|
||
**Ожидание: −50 000 … −60 000.**
|
||
|
||
У оригинала `draw_tile` (seg008:01C7) — это девять независимых вызовов:
|
||
`draw_tile_floorright`, `draw_tile_anim_topright`, `draw_tile_right`,
|
||
`draw_tile_anim_right`, `draw_tile_bottom`, `draw_loose`, `draw_tile_base`,
|
||
`draw_tile_anim`, `draw_tile_fore`. Для ряда −1 он зовёт шесть из них
|
||
(`draw_tile_aboveroom`, seg008:01F2), для полосы у потолка — те же шесть плюс
|
||
`draw_tile_wipe(3)` (`redraw_needed_above`, seg008:02C1).
|
||
|
||
У нас всё это — ветки `if (row >= 0)` ВНУТРИ одной функции, то есть контекст
|
||
(13 584) и диспетчер (11 388) оплачиваются целиком всегда. Расколов, каждая
|
||
точечная перерисовка сможет звать только нужные части:
|
||
|
||
- `pop_floor_bake`: вместо второго полного `draw_tile(row, col+1)` — только
|
||
его правую грань и базу;
|
||
- `pop_ceil_shake_draw` / `pop_ceil_bake_empty`: дословный
|
||
`draw_tile_aboveroom`;
|
||
- `pop_loose_shake_draw`, `pop_spike_redraw`, `pop_gate_redraw` — то же.
|
||
|
||
Риск средний: у `draw_tile` собрано много инвариантов (BUG-LOOSE-3,
|
||
BUG-LATTICE-DOORTOP, BUG-SEAM-WEDGE-1), проверять придётся прогонами всех
|
||
уровней. Поэтому делать отдельным заходом, а не хвостом другой правки.
|
||
|
||
### G6. Меньше блитов в `RD_FLOOR`
|
||
|
||
**Ожидание: неизвестно, надо мерить.** 105 500 из 179 914 — это 7,6 блита,
|
||
и они рисуют настоящие пиксели. Сократить можно только сократив то, что
|
||
восстанавливается: бар сейчас 60×39 от `yb+26`, а плита занимает по вертикали
|
||
меньше (её куски: левая грань `POP_LOOSE_FRAM_LEFT` 32×13 на `dmy = yb+62`,
|
||
низ `POP_LOOSE_FRAM_BOTTOM` 32×3 на `dby = yb+65`, правая грань в соседе
|
||
26×16 на `dby−1`). То есть плита живёт в `yb+47 .. yb+65`, а бар начинается с
|
||
`yb+26` — **21 лишняя строка сверху**.
|
||
|
||
Проверять осторожно: бар заодно стирает и то, что рисует ДРУГИЕ куски тайла
|
||
(орнаментная лента `stripe_id` на `dmy−27 = yb+35` попадает как раз в
|
||
«лишнюю» часть). Сузишь бар — надо убедиться, что ничего не осталось.
|
||
|
||
### G7. heal летящих кусков — 154 074 (28 % фазы)
|
||
|
||
Шесть кусков × ~25 700: сам heal 64×20 (по модели ~19 700) + пакетная
|
||
пометка + накладные `mob_tick_one` (16-байтовый кадр, 99 обращений `(ix)`).
|
||
**Сам heal у предела железа** — это 1 280 пикселей на кусок, оптимизировать
|
||
нечего, кроме площади. Площадь уже подрезана до габарита композита.
|
||
|
||
Остаётся `mob_tick_one` (~4 500 на кусок = 27 000 на кадр) — то же лечение
|
||
file-scope, что у `draw_tile`.
|
||
|
||
### G8. Пометка соседа — узкой полосой, а не полным тайлом (идея пользователя)
|
||
|
||
**Ожидание: заметное, но не мерено. Взять ПОСЛЕ обхода всех уровней**
|
||
(решение пользователя 2026-08-17: пока идёт отлов багов слоёв, каждая правка
|
||
добавляет переменных в картину).
|
||
|
||
Когда плита (1,8) падает, помечаются ДВА тайла:
|
||
|
||
| пометка | что делает |
|
||
|---|---|
|
||
| `(1,8)` → `RD_LOOSE_GONE` | бар 40 на своём x, бар 32 на соседе, `draw_tile(1,8)` + `draw_tile(1,9)` |
|
||
| `(1,9)` → `RD_FLOOR` | бар **60** на x соседа, `draw_tile(1,9)` ЕЩЁ РАЗ |
|
||
|
||
То есть сосед перезапекается ЦЕЛИКОМ и ПОВТОРНО, хотя потревожили у него
|
||
только левые 28 пикселей — там, куда свисает правая грань упавшего тайла.
|
||
`draw_tile(1,9)` при этом вызывается дважды на одну пометку.
|
||
|
||
Что такое эти числа (чтобы не сузить лишнего):
|
||
|
||
- **60 = 32 свой тайл + 28 СОБСТВЕННЫЙ свес.** Правая грань пола (кадр 42,
|
||
26 px) рисуется в клетке соседа с `x+32`, занимая `x+32..x+57`. Для запечки
|
||
САМОГО тайла 60 уже минимальны — сужать их нельзя;
|
||
- сузить можно только тот случай, когда тайл помечен ПОТОМУ ЧТО ИЗМЕНИЛСЯ ЕГО
|
||
ЛЕВЫЙ СОСЕД: тогда нужна полоса 28 px у левого края, а не весь тайл.
|
||
|
||
Почему выигрыш не символический: `pop_floor_bake` стоит **179 914** тактов,
|
||
из них 105 500 — сами блиты. Узкая полоса срезала бы и площадь бара
|
||
(60×39 → 28×39), и часть блитов — окно клипа там теперь стоит обязательным
|
||
(см. §4), так что отсев достаётся даром.
|
||
|
||
**Условия, из-за которых это не «просто уменьшить число»:**
|
||
|
||
1. `pop_floor_bake` — ОБЩАЯ функция: её же зовут кнопка (`pop_button_redraw`),
|
||
зеркало, подобранный предмет и щебень на месте посадки. Там меняется сам
|
||
тайл и 60 нужны целиком. Значит нужен отдельный вход (напр.
|
||
`pop_floor_bake_edge(row, col)`) или параметр-прямоугольник — именно под
|
||
пометку «изменился мой левый сосед».
|
||
2. Прежде чем выкидывать вторую пометку целиком, сверить ВЕРТИКАЛЬНЫЕ
|
||
диапазоны: `pop_loose_bake_empty` кроет `63*row+46 .. +65` (20 строк), а
|
||
`pop_floor_bake` — `yb+26 .. yb+64` (39 строк). То есть сосед покрыт
|
||
ВТОРЫМ баром не полностью, и просто снять пометку нельзя.
|
||
3. Ширина полосы = свес ЛЕВОГО тайла, а он зависит от типа тайла (у loose это
|
||
8 px по комментарию в `pop_loose_bake_empty`, у пола 26). Брать по
|
||
максимуму (28) — безопасно.
|
||
|
||
### G9. Снять временную оснастку
|
||
|
||
Шесть `pop_dbg_b1..b6` внутри `pop_blit_b` — ~400 такта на блит; при 15
|
||
блитах зелёной это 6 000 на кадр. Плюс `pop_dbg_kind`/`m16` (2 вызова на
|
||
перерисовку) и `pop_dbg_m5..m15`. Снимать ПОСЛЕ окончания оптимизации: без
|
||
них не мерить.
|
||
|
||
---
|
||
|
||
## 4. Копия второй страницы — и почему она ТРЕБУЕТ окна клипа
|
||
|
||
Точечные запечки ставятся с `pages = 2`, срабатывают два кадра подряд (по разу
|
||
на страницу дабл-буфера) и оба раза считают одно и то же. После первого раза
|
||
нужный прямоугольник уже лежит в ОЗУ-копии первой страницы, и его можно
|
||
скопировать: `gfx_copy_page` берёт источником ОЗУ-копию НЕактивной страницы
|
||
(то есть ЧИСТЫЙ фон — спрайты рисуются банком без тени и в копию не попадают),
|
||
а приёмник обновляет и в видео-ОЗУ, и в ОЗУ-копии. Идея пользователя: тот же
|
||
приём, что при перевороте экрана (зелёное зелье), только без зеркала.
|
||
|
||
| | полная запечка | копия |
|
||
|---|---:|---:|
|
||
| щебень / кнопка (60×39) | 179 914 | ~35 000 |
|
||
| колодец полосы потолка (64×9) | 138 318 | ~17 500 |
|
||
|
||
**ДВА УСЛОВИЯ КОРРЕКТНОСТИ.** Оба нарушались и оба дали видимые баги.
|
||
|
||
1. **Запечка обязана быть ОГРАНИЧЕНА копируемым прямоугольником.**
|
||
`draw_tile` рисует тайлы ЦЕЛИКОМ, то есть пишет ШИРЕ бара; копия переносит
|
||
ровно бар, и всё, что легло вне него, на второй странице остаётся прежним —
|
||
страницы расходятся, это видно как МЕРЦАНИЕ через кадр. У полосы потолка
|
||
окно стояло с самого начала (G1), у `pop_floor_bake` — нет, и он мерцал
|
||
торцами полов, плит и кнопок (найдено пользователем 2026-08-17: уровень 1,
|
||
комната 6, Кид на кнопке (0,2)). Поэтому в `pop_floor_bake` окно теперь
|
||
стоит КАК УСЛОВИЕ КОРРЕКТНОСТИ, хотя по скорости само по себе убыточно
|
||
(см. §5) — снимать его нельзя.
|
||
2. **Копия годится только если содержимое тайла между двумя кадрами не
|
||
изменилось.** У анимированного тайла (кнопка с идущим таймером связи)
|
||
пометка обновляется КАЖДЫЙ кадр и картинка каждый раз другая. Поэтому
|
||
`pop_set_redraw`/`pop_set_redraw_above` гасят слот копии при ПЕРЕпометке
|
||
(`pop_bake_slot_reset*`).
|
||
|
||
Плюс слот `bake_pg`/`bake_pg_above` помнит, НА КАКОЙ странице сделана первая
|
||
запечка: копируем только если первая была на ДРУГОЙ странице и дабл-буфер
|
||
включён. Это покрывает переплетение двух запечек в одном кадре, однобуфер
|
||
(чит SPACE) и смену комнаты (`pop_bake_forget`).
|
||
|
||
---
|
||
|
||
## 5. Отрицательные результаты — НЕ повторять
|
||
|
||
### Окно клипа в `pop_floor_bake` — по СКОРОСТИ проверено ТРИ раза, каждый раз хуже
|
||
|
||
**Но оно всё равно стоит на месте: без него ломается копия второй страницы
|
||
(§4).** Ниже — только про скорость самого окна.
|
||
|
||
| попытка | было | стало |
|
||
|---|---:|---:|
|
||
| до G3 | 187 266 | 198 279 |
|
||
| после G3 | 182 124 | 188 460 |
|
||
| после `pop_blit_b` file-scope, с детальным зондом | 179 914 | 188 417 |
|
||
|
||
Третья попытка объяснила причину: клипованный путь стоит **+1 500 такта на
|
||
КАЖДОМ** из 7,6 блитов (+11 400), а режет он только редкие высокие куски — в
|
||
трассе такие нашлись (34 878 → 25 872 и 28 818 → 24 090, всего −13 700), но в
|
||
среднем по 12 перерисовкам их нет. Запись стоит в коде.
|
||
|
||
### Прочее (проверено раньше)
|
||
|
||
- **Не откладывать запекание на другой кадр** — запечка пишет ОЗУ-копию, из
|
||
которой восстанавливает heal; отложенная даёт призрак плиты на месте дыры.
|
||
- **Не батчить смежные колонки в `pop_ceil_shake_draw`** — плиты стартуют со
|
||
случайными задержками, в кадре дрожат разрозненные колонки, пробег почти
|
||
всегда длиной в одну.
|
||
- **Не ускорять передачу пикселей** — предел железа (3+3 такта на байт,
|
||
memory `blit_cost_model`). В пиковом кадре «железный» минимум всех блитов
|
||
≈150 000 из 916 458.
|
||
- **`LOOSE-SHAKE-RUNS`** (пометки по сменам кадра): наивный вариант выигрыша
|
||
НЕ даёт — пять смен × две страницы = те же десять перерисовок. Работает
|
||
только версия «пары и тройки», ~20 % и только на дрожащих плитах; оценка
|
||
2026-08-13, не перемерена. Подробности —
|
||
`TASKS_OPEN.md#loose-shake-runs`.
|
||
|
||
---
|
||
|
||
## 6. Журнал правок
|
||
|
||
| дата | что сделано | зелёная: покой / дрожь / пик | коммит |
|
||
|---|---|---|---|
|
||
| 2026-08-17 | базовый замер | 35 760 / 366 000 / **805 000** | `c312e4a` |
|
||
| 2026-08-17 | G2 контекст тайла в file-scope | — / — / **747 954** | `a3c473d` |
|
||
| 2026-08-17 | G1 окно клипа в `pop_ceil_bake_empty` | — / — / **663 250** | `a3c473d` |
|
||
| 2026-08-17 | G3 `blit_b_clip` байты + file-scope | — / 335 400 / **575 730** | `a3c473d` |
|
||
| 2026-08-17 | `pop_blit_b` file-scope; пакетная пометка кусков | — / — / **557 706** | `b2da0b8` |
|
||
| 2026-08-17 | коридор heal по высоте композита | 35 760 / 335 400 / **546 900** | `9a50ab2` |
|
||
| 2026-08-17 | **копия второй страницы вместо второй запечки** | — / — / **423 558** | `5ef721e` |
|
||
| 2026-08-17 | `mob_tick_one` в file-scope; снята оснастка из горячих путей | 35 760 / 326 130 / **414 456** | `18ee60e` |
|
||
| 2026-08-17 | фикс мерцания: окно клипа в `pop_floor_bake` как условие корректности копии | замер после фикса — ниже | `35b7cd5` |
|
||
| 2026-08-17 | замер после фиксов уровня 1 (мерцание торцов, потолочный fore, сосед под плитой, блеск меча) | 35 760 / — / **417 630** | `40f0d46` |
|
||
| 2026-08-17 | **регресс после фиксов уровня 2** (чёрные бары, чит бессмертия) — в пределах шума | — / — / **419 526** | `ec1f384` |
|