Меню, статус-строка и оболочка игры: title/intro/cutscene/HoF, POP.CFG, палитры
Эта сессия (меню + текст в служебных полосах):
* Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и
снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках
(UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space —
равноправные клавиши действия (ui_action_down).
* Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и
ABOUT. About показывает тот же текст, что стартовый Sprinter screen,
минус строка про клавиши — общий about_text(), чтобы экраны не
разъехались. CONTROLS собирается таблицей и центрируется по
фактическому числу строк.
* GAME PAUSED переехала в нижнюю статус-строку, как в оригинале
(SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы,
а не пункт меню. POP_HP_Y вынесен в pop_cdraw.h — полосу делят два
модуля.
* pop_status.c (банк 9) — текст в обеих служебных полосах. Нижняя:
порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF,
24 тика). Верхняя отладочная переведена с палочек на текст
«Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с
своим форматированием чисел (без printf и без деления).
Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2
не растёт, весь рендер в банке. Бюджет после правок не изменился
(_CODE 23981, куча 267 Б).
* Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому
всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы
не блитятся вовсе, а вход в комнату заливает только игровое поле
(pop_screen_fill_field) — борта от комнаты к комнате не меняются.
* Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие
ворот (4) и открытие двери покоев (51), из которой входит Джафар.
* docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в
статус-строке: геометрия, семантика text_time_total как идентификатора
сообщения, мигание, рестарт по истечении 36/288.
Вместе с этим выкладывается накопленная работа по оболочке полной игры:
автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo,
Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и
fade, звуковой набор, host-тесты на новые швы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,172 @@
|
||||
|
||||
---
|
||||
|
||||
<a id="cutscene-skip-held-key"></a>
|
||||
## CUTSCENE-SKIP-HELD-KEY. Катсцены схлопывались в один кадр (и финал — в чёрный экран) — **ЗАКРЫТ 2026-08-25**
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-24/25).** Сначала: после 14-го уровня
|
||||
вместо финала чёрный экран. Затем, после первого фикса: во ВСЕХ сценах
|
||||
(8, 9, 12 и финал) показывается один кадр — и сразу следующий уровень.
|
||||
|
||||
**Корень.** Все сцены проверяют «не просят ли пропустить» через
|
||||
`kbd_raw_any_down()` на КАЖДОЙ итерации, начиная с первой. А клавиша,
|
||||
которой закончили уровень, в этот момент ещё зажата: в финальную комнату Кид
|
||||
ВБЕГАЕТ (стрелка удерживается), дверь уровня проходят на ходу, чит Shift+L
|
||||
тоже удерживается. Удержание засчитывалось как запрос skip.
|
||||
|
||||
Первый фикс (ждать отпускания перед сценой) проблему только сдвинул: ждать
|
||||
безусловно нельзя — потерянный break-код вешает игру намертво
|
||||
([KBD-STUCK-WAIT](BUGS_OPEN.md#kbd-stuck-wait)), — а с таймаутом в секунду
|
||||
сцена всё равно стартовала при зажатой клавише и гасла на первом же кадре.
|
||||
|
||||
**Правка.** Детектор ФРОНТА (`intro_skip_begin`/`intro_skip_requested`):
|
||||
пока со старта сцены хоть что-то зажато, skip не взводится; как только всё
|
||||
отпущено — следующее нажатие сцену прерывает. Применён во всех четырёх
|
||||
местах: `intro_run`, `pre_princess_animated`, `intro_pv_animated`, `cut_run`.
|
||||
Ожидание отпускания убрано за ненадобностью. Залипший бит в худшем случае
|
||||
делает сцену непропускаемой — она доиграет сама, игра не встанет.
|
||||
|
||||
**Проверка.** MAME: переход 7 -> 8 читом (клавиша удерживается) — сцена 8
|
||||
отыгрывает целиком, видно сидящую принцессу и мышь; финал (уровень 14,
|
||||
комната 5) — принцесса поворачивается и обнимает Кида, затем мышь, затем
|
||||
текст HAIL.
|
||||
|
||||
---
|
||||
|
||||
<a id="gate-safe-step-short"></a>
|
||||
## GATE-SAFE-STEP-SHORT. Перед ОТКРЫТОЙ решёткой осторожный шаг вдвое короче — **ЗАКРЫТ 2026-08-24**
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-24).** Идя слева направо к поднятой
|
||||
решётке, Кид делает лишний мелкий шаг: чтобы подойти к краю, Shift+вперёд
|
||||
надо нажать дважды.
|
||||
|
||||
**Корень.** Длину осторожного шага выбирает сам `safe_step` (seg005:0604) по
|
||||
числу из `get_edge_distance`: последовательности 29..42 — это «шаг ровно на
|
||||
distance пикселей». А `dist_from_wall_forward` у оригинала ПЕРВОЙ строкой
|
||||
отвечает −1, если тайл — ворота, сквозь которые можно пройти (seg004:0A18:
|
||||
`if (tiletype == tiles_4_gate && !can_bump_into_gate()) return -1`); минус
|
||||
единица уводит разбор дальше, где открытые ворота опознаются обычным полом и
|
||||
дают `distance = 11`. В нашем порту этой строки не было: расстояние честно
|
||||
считалось до створки (у ворот `wall_type = 1`, «стена справа»), край
|
||||
классифицировался как `EDGE_WALL`, и шаг выходил коротким.
|
||||
|
||||
**Правка.** Ветка возвращена в `dist_from_wall_forward` (pop_map.c),
|
||||
проходимость берётся у нашего `gate_passable` (это и есть
|
||||
`!can_bump_into_gate`).
|
||||
|
||||
**Проверка.** Хост-набор `tests-host/t_gate.c` (новый): на одном и том же X
|
||||
открытая решётка обязана давать то же, что чистый пол, а закрытая — остаться
|
||||
`EDGE_WALL`. Со снятым фиксом набор падает ровно на симптоме: `d = 9`,
|
||||
`EDGE_WALL` вместо `11`, `EDGE_FLOOR`.
|
||||
|
||||
---
|
||||
|
||||
<a id="gate-superposition"></a>
|
||||
## GATE-SUPERPOSITION. Кид остаётся в тайле опустившейся решётки — **ЗАКРЫТ 2026-08-24**
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-24).** Кид встал в тайл решётки, пока та
|
||||
была поднята; решётка опустилась — Кид остался внутри. Дальше он «в
|
||||
суперпозиции»: лицом влево рисуется ЗА решёткой, лицом вправо — ПЕРЕД ней, и
|
||||
уйти может в обе стороны. То же самое — при попытке проползти под
|
||||
опускающейся решёткой в присяде.
|
||||
|
||||
**Корень.** В оригинале выталкиванием занимается ОТДЕЛЬНАЯ функция кадровой
|
||||
цепочки — `check_gate_push` (seg004:0833, зовётся из play_kid_frame,
|
||||
seg000:1241, между `check_bumped` и `check_action`). В нашем `kid_phys` её
|
||||
не было вовсе: `check_bumped` ловит только движение В препятствие, а
|
||||
«препятствие появилось вокруг стоящего» — случай именно `check_gate_push`.
|
||||
|
||||
**Правка.** Порт добавлен в pop_map.c и вставлен в `kid_phys` на своё место.
|
||||
Условия оригинала сохранены: срабатывает только в позах «стоит на месте» —
|
||||
разворот (action 7), стойка (кадр 15), присед (кадры 108..110); решётка
|
||||
ищется в своём тайле И в тайле слева (створка нарисована у правой грани);
|
||||
перекрытие обязано быть обеими гранями и в этом кадре, и в прошлом; толчок —
|
||||
5 пикселей влево (решётка своя) либо вправо (решётка левого соседа) +
|
||||
`bumped_sound`.
|
||||
|
||||
---
|
||||
|
||||
<a id="guard-droppedout-carry"></a>
|
||||
## GUARD-DROPPEDOUT-CARRY. Страж идёт в челюсти: `droppedout` пережил границу уровня — **ЗАКРЫТ 2026-08-24**
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-24).** Уровень 4, комната 22: страж без
|
||||
всякого повода идёт навстречу Киду и гибнет в челюстях; если Кид спустился
|
||||
рядом ниже и челюсти остановились — страж проходит сквозь них и сваливается к
|
||||
Киду. Воспроизводилось несколько раз, но ТОЛЬКО когда на 4-й уровень
|
||||
приходили ногами, через дверь 3-го; при отладочном старте прямо на 4-м
|
||||
(`make LEVEL=4 ROOM=22`) сцена вела себя правильно — страж стоял, боя не было.
|
||||
|
||||
**Корень.** Единственная ветка, которая двигает стража ВПЕРЁД при
|
||||
`can_guard_see_kid == 0`, — `guard_follows_kid_down()` (seg002:0806), и гейтит
|
||||
её флаг `droppedout`: «Кида спихнули из боя вниз — идти ли следом». Флаг
|
||||
взводит `start_fall` (seg006:1123, у нас pop_map.c), когда персонаж срывается
|
||||
с уступа В БОЕВОЙ ПОЗЕ (кадры 150..179) — то есть обычная концовка боя со
|
||||
скелетом на 3-м уровне. В оригинале его гасит `set_start_pos()`
|
||||
(seg003:028A) при старте КАЖДОГО уровня; в нашем порту (pop_start_level,
|
||||
roomtest_cold.c) этого сброса не было, и флаг переезжал на следующий уровень.
|
||||
|
||||
Дальше всё сходится: `can_guard_see_kid` считался ПРАВИЛЬНО (в живой сцене
|
||||
прочитано `1` — чомпер в луче даёт «вижу, но не пойду»), но активная ветка
|
||||
при 0 уходила не в «попятиться» (`move_down_back`), а в «идти за упавшим
|
||||
Кидом», а она смотрит только на пол впереди и внизу — чомпер ей не помеха.
|
||||
|
||||
**Правка.** В порт `set_start_pos` возвращены сбросы оригинала:
|
||||
`pop_droppedout = 0`, `offguard = 0`, `knock = 0` (fall_x/fall_y уже обнуляет
|
||||
`kid_init`).
|
||||
|
||||
**Проверка.** MAME, сборка `make LEVEL=4 ROOM=22 POS=0`: флаг взведён
|
||||
отладчиком (`pop_droppedout = 1`), затем меню -> RESTART LEVEL -> флаг снова
|
||||
0, страж стоит. Само старое поведение вживую НЕ переигрывалось: причина
|
||||
доказана кодом (в оригинале сброс есть, у нас его не было) и тем, что
|
||||
единственный путь стража вперёд при `can_guard_see_kid == 0` гейтится этим
|
||||
флагом.
|
||||
|
||||
**Урок.** Меж-уровневые флаги проверять поимённо по `set_start_pos`:
|
||||
симптом вылезает НЕ там, где флаг взводится, и не воспроизводится отладочным
|
||||
стартом с нужного уровня — именно потому, что тот стартует с чистым
|
||||
состоянием.
|
||||
|
||||
---
|
||||
|
||||
<a id="pal-skel-guard"></a>
|
||||
## PAL-SKEL-GUARD. Скелет (уровень 3) розовый — палитру набора затирал kid.pal — **ЗАКРЫТ 2026-08-24**
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-24).** Уровень 3, комната 1: поднявшийся
|
||||
скелет нарисован розово-телесным вместо костяного белого. Кид, фон и факелы —
|
||||
верные.
|
||||
|
||||
**Корень.** Все пиксели спрайтов скелета — ОДИН индекс 3 его набора
|
||||
(`SKEL/res751..res778.png`, проверено гистограммой), то есть слот `0x93`.
|
||||
Палитру набора `pop_guard_load()` заливал ОДИН раз, сразу после атласов, а
|
||||
дальше по маршруту загрузки уровня шёл `pop_pal_level_load(1)` →
|
||||
`pop_pal_game_load()` → `gfx_pal_fload("KID\\kid.pal")`. `kid.pal` содержит
|
||||
ВСЕ 256 записей, включая диапазон стража `0x90..0x9F` с запечённым цветом 2
|
||||
(`pop_pack_guard.py`, `COLOR = 2`), — и он затирал палитру скелета. Слот
|
||||
`0x93` цвета 2 = `(FF,79,79)`, ровно тот розовый, что был на экране. Тот же
|
||||
механизм ждал Джафара на уровне 13 (`tbl_guard_type == 3`, палитра тоже из
|
||||
файла набора).
|
||||
|
||||
Обычный страж симптома не давал только по случайности: у него цвет
|
||||
переставляет `pop_guard_set_palette()` при входе в комнату (enter_guard), то
|
||||
есть ПОСЛЕ file-load. У скелета и визиря `curr_guard_color == 0` (seg002:186)
|
||||
— пере-установки нет вовсе.
|
||||
|
||||
**Правка.** `0x90..0x9F` — такой же ДИНАМИЧЕСКИЙ диапазон, как тайлсет и Тень,
|
||||
и обязан доплачиваться после каждого file-load логической палитры. Добавлен
|
||||
`pop_guard_pal_apply()` (pop_cdraw.c, банк 4) — пара к `pop_bg_pal_apply` /
|
||||
`pop_shadow_pal_apply`: набор со своим цветом (скелет/Джафар) берёт палитру из
|
||||
своей таблицы, обычный страж — последний `curr_guard_color`
|
||||
(`guard_pal_cur`, его пишет `pop_guard_set_palette`). Зовётся из
|
||||
`pop_pal_game_load()` и `pop_pal_level_load(0)` ДО snapshot, чтобы правильный
|
||||
цвет попал и в снимок для fade. `pop_guard_load()` больше не грузит палитру
|
||||
сам, а зовёт ту же функцию.
|
||||
|
||||
**Проверка.** MAME, сборка `make LEVEL=3 ROOM=1 POS=12`, `pop_leveldoor_open`
|
||||
взведён отладчиком, чтобы скелет поднялся сразу. Скелет белый; цвет пикселей
|
||||
на скриншоте `(255,255,242)` — ровно запись 3 `pop_skel_pal`.
|
||||
|
||||
---
|
||||
|
||||
<a id="grab-below-room"></a>
|
||||
## GRAB-BELOW-ROOM. Зацеп за кромку НИЖНЕГО ряда при пролёте вниз не работал вовсе
|
||||
|
||||
|
||||
Reference in New Issue
Block a user