L1: фиксы прогона уровня 1 — уровень мутабелен, стражи, loose-плиты, порядок
Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём. Разбор каждого — bug_closed.md.
Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
(POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
роняло Кида сквозь пол.
Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
(redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.
По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
нарисовался поверх передних столбов (kid_fore_clip_restore);
2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
настоящей X. Теперь колонка выводится из X, как в оригинале;
3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
«упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
габарита, поэтому бегущий Кид уходит за объекты справа. Считается
для обоих персонажей — enter_guard ставит action=1 и стражу.
Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража. make size-check — роста нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -398,3 +398,228 @@ MAME по кромкам.
|
||||
Следствие: в 13/18/24 возможен «мусор в шве» — наш рендер кромки читает
|
||||
крайнюю колонку соседа ПО ССЫЛКЕ, а сосед этих комнат соседом себя не
|
||||
считает. Приоритет багов в этих трёх комнатах — низкий (в игре не видно).
|
||||
|
||||
---
|
||||
|
||||
## Проверено в MAME 2026-08-03 (прогон уровня 1: 11 наблюдений → 6 корней)
|
||||
|
||||
Сырой список наблюдений с обхода всех комнат разобран по корням.
|
||||
Проверка — MAME + мост `mame-z80`: чтение `_Kid` по адресу из
|
||||
`.sprinter-cc-roomtest/roomtest.noi`, `ROOMNAV` для навигации, потиковые
|
||||
трассы, скриншоты.
|
||||
|
||||
**Оговорка о полноте проверки.** Каждый корень закрыт тем, что его СОБСТВЕННЫЙ
|
||||
сценарий больше не воспроизводится; сквозного прохождения уровня и поиска
|
||||
регрессий в соседней механике автоматика не делала. Чек-лист для ручной
|
||||
перепроверки — в [`bug_list.md`](bug_list.md), раздел «Ручная перепроверка
|
||||
фиксов». Особое внимание — порту `check_collisions`: он переписал ВСЮ
|
||||
горизонтальную коллизию.
|
||||
|
||||
### BUG-LVLSTATE-1. Уровень был немутабельным — **ЗАКРЫТ**
|
||||
|
||||
**Симптомы:** выпитое зелье / поднятый меч / разбитая плита возвращались при
|
||||
возврате в комнату; иногда кувшина нет, а пузырёк над ним крутится; в
|
||||
комнате 15 раз в 1–2 с мигал контур кладки. «Первое остаётся, остальное
|
||||
возвращается».
|
||||
|
||||
**Корень.** Уровень лежал в EMM-странице только на чтение, `enter_room`
|
||||
перезаливал `room_fg[30]` из неё при каждом входе, а персистентность держала
|
||||
таблица переопределений на **8 записей** (`OVR_MAX` в `roomtest.c`) — девятая
|
||||
и дальше молча терялись. Второй хвост: `pop_trob.c` брал тип тайла из
|
||||
СТРАНИЦЫ (`pop_level_tile_raw`), поэтому заводил trob зелья/меча там, где
|
||||
предмет уже поднят — отсюда пузырёк без кувшина и мигание от `animate_sword`.
|
||||
|
||||
**Фикс.** `pop_level_set_tile()` пишет тайл ПРЯМО в страницу уровня (порт
|
||||
`curr_room_tiles[…] = …`: `do_pickup` seg006:1671, `remove_loose` seg007:0EB8,
|
||||
`loose_land` seg007:11E8). Таблица `ovr_*` удалена. Эталонная копия
|
||||
foretable — в той же странице по смещению `0x1000` (страница 16 КБ, данных
|
||||
2.3 КБ).
|
||||
|
||||
**Проверено:** комната 22, зелье (0,6) выпито → выход в 23 → возврат: кувшина
|
||||
нет, пузырька нет; ROOMNAV ставит Кида уже на (0,6), потому что тайл стал
|
||||
полом.
|
||||
|
||||
### BUG-RESPAWN-1. Респавн не перезагружал уровень — **ЗАКРЫТ**
|
||||
|
||||
**Как в оригинале:** цикл `play_level` (seg003:57) на КАЖДОЙ итерации, в том
|
||||
числе после смерти, зовёт `load_level()` — уровень читается заново.
|
||||
|
||||
**Фикс.** `pop_level_reset_tiles()` (восстановление foretable из эталонной
|
||||
копии) в `pop_start_level` рядом с `pop_trob_reset()`.
|
||||
|
||||
**Проверено:** после смерти от стража зелье в комнате 22 снова на месте.
|
||||
|
||||
### BUG-DEATH-1. Смерть от меча не доводилась до конца — **ЗАКРЫТ**
|
||||
|
||||
**Симптом:** страж убивает Кида, тот «воскресает» на месте и его убивают
|
||||
снова, по кругу.
|
||||
|
||||
**Корень.** `hurt_by_sword` ставил seq_85 и обнулял HP, но `Kid.alive`
|
||||
оставался −1, а `pop_kid_dead` (по нему главный цикл делает респавн) взводил
|
||||
только путь пик/падения. В оригинале это первая строка `control_kid`
|
||||
(seg006:0CD1): `if (Char.alive < 0 && hitp_curr == 0) Char.alive = 0;`.
|
||||
|
||||
**Фикс.** Порт этой ветки в начало `pop_ctrl_tick` + `pop_kid_dead = 1`.
|
||||
|
||||
**Проверено:** страж в комнате 21 убивает Кида → респавн в стартовой позиции
|
||||
уровня (комната 1), цикла нет.
|
||||
|
||||
### BUG-GATE-ANIM-1. Ворота в отрисованной комнате не перерисовывались — **ЗАКРЫТ**
|
||||
|
||||
**Корень.** `pop_process_trobs` продвигал модификатор ворот, но пометки
|
||||
перерисовки не ставил; ворота рисовались только при полной отрисовке комнаты
|
||||
и в `pop_room_redraw_seam_left`. В оригинале `animate_door` (seg007:0522)
|
||||
заканчивается `draw_trob()` (seg007:01E6).
|
||||
|
||||
**Фикс.** Вид перерисовки `POP_RD_GATE` + `pop_gate_redraw(row,col)` в
|
||||
`pop_bg.c` (wipe зоны решётки в ячейке ПРАВОГО соседа + `draw_tile`), пометка
|
||||
из `pop_process_trobs` (текущая страница каждый кадр, обе — на последнем).
|
||||
|
||||
**На уровне 1 это ровно ОДНА решётка:** room5 (0,5). Все остальные стоят в
|
||||
колонке 9, их бары рисуются уже в соседней комнате — там работает
|
||||
`pop_room_redraw_seam_left`.
|
||||
|
||||
**Проверено:** кнопка room5 (0,4) — решётка (0,5) поднимается на экране.
|
||||
|
||||
### BUG-COLL-1. Bump искал стену только в колонке переднего края — **ЗАКРЫТ**
|
||||
|
||||
**Симптомы:** пробегание сквозь закрытую решётку (комната 12, room5 (0,9));
|
||||
влёт внутрь стены на длинном прыжке (комната 6).
|
||||
|
||||
**Два корня.**
|
||||
1. `check_bumped` брал ОДНУ колонку — ту, в которой оказался передний край.
|
||||
Для решётки в col9 окно этой колонки всего 4 px (201..204), беговой кадр
|
||||
его перескакивает — бампа нет, а `check_leave` тут же уводит в соседнюю
|
||||
комнату. Оригинал (`check_collisions`, seg004:0004) считает флаги
|
||||
перекрытия для ВСЕХ колонок ряда и берёт колонку с переходом флага 0→1.
|
||||
2. Наш guard `action == FREEFALL || MIDAIR → return` в `check_bumped`. В
|
||||
оригинале таких гардов НЕТ: `bumped_fall` (seg004:04E4) специально
|
||||
разбирает `actions_4_in_freefall`. Отсюда влёт в стену в прыжке.
|
||||
|
||||
**Фикс.** Полный порт `check_collisions` + `get_row_collision_data` +
|
||||
`is_obstacle` + `bumped(delta, push_dir)`; колонки считаются от −2 до 11,
|
||||
чтобы решётки СОСЕДНЕЙ комнаты (швы) бампили как свои; гарды в `check_bumped`
|
||||
приведены к оригинальным (только вис и подтягивание).
|
||||
|
||||
**Проверено:** комната 5, бег вправо в закрытую решётку (0,9) — Кид упирается
|
||||
(x встаёт на 60 в системе комнаты 1 и дальше не растёт).
|
||||
|
||||
**Остаток:** экран при этом перелистывается на соседнюю комнату, и Кид в шве
|
||||
не рисуется — отдельный баг BUG-SEAM-DRAW-1 (модель straddle S3), см.
|
||||
`bug_list.md`.
|
||||
|
||||
### BUG-STANDUP-1. Вставание из приседа у стены роняло сквозь пол — **ЗАКРЫТ**
|
||||
|
||||
**Симптом:** комната 5, падение с кнопки (0,6) на щебень (2,7) с уроном,
|
||||
присед — и при вставании провал в комнату 6.
|
||||
|
||||
**Корень — лишний guard `if (Kid.action == ACT_BUMPED) return;` в
|
||||
`bumped_floor`** (в оригинале, seg004:0520, там `if (Char.alive)`). Цепочка:
|
||||
вставание двигает Кида на 1 px в стену → бамп → `bumped_floor` прижимает `y`
|
||||
к полу, отменяя `dy(−2)` из начала `medland` → наш guard возвращает
|
||||
управление вместо `seq_47`, `medland` доигрывает свои `dy(+1)`,`dy(+1)` уже
|
||||
ОТ пола → Кид НИЖЕ пола → следующий бамп читает беззнаковую разность как
|
||||
«высоко над полом» → `bumped_fall` → выпадение вниз. У оригинала `seq_47`
|
||||
обрывает `medland`, лишних `dy` нет.
|
||||
|
||||
Найдено потиковой трассой + временной диагностикой (колонка бампа = 8, тайл
|
||||
после разрешения = 14 щебень, ветка = `bumped_floor`).
|
||||
|
||||
**Заодно** убран полу-порт опционального `FIX_STAND_ON_THIN_AIR`: у нас была
|
||||
взята только его первая часть (кадры вставания 110..119 требуют пол), без
|
||||
парной правки seqtbl (`dx(1)→dx(0)`, `dx(−4)→dx(−3)`), которую применить
|
||||
нельзя — seqtbl извлечён из данных оригинала. Вернулись к ванильному
|
||||
`seg006:909` (только кадр 109).
|
||||
|
||||
---
|
||||
|
||||
# Вторая волна прогона 2026-08-03 (вечер)
|
||||
|
||||
<a id="bug-kbd-4"></a>
|
||||
## BUG-KBD-4 (+ BUG-KBD-3). Shift: сначала залипал, потом стал отваливаться — **ЗАКРЫТ**
|
||||
|
||||
**Симптом (вторая редакция).** Залипание ушло, но появилось обратное: при
|
||||
зажатом Shift второй-третий-четвёртый тап стрелки отрабатывал уже не
|
||||
осторожным шагом, а бегом. Для PoP это ХУЖЕ залипания: игрок рассчитывает
|
||||
на короткий шаг, а Кид убегает в яму или на пики.
|
||||
|
||||
**Почему обе прежние редакции были неправильны.** Это был размен между
|
||||
двумя способами «починить» потерю байта при Rx-overrun SIO (FIFO 3 байта):
|
||||
|
||||
1. исключать модификаторы из сброса — потерянный break Shift снять нечем,
|
||||
Shift залипает навсегда (BUG-KBD-3);
|
||||
2. сбрасывать всю карту, как DSS (`KBD_Receiver_Overrun` в `KEYINTER.ASM`
|
||||
чистит и `KEYCTRL`, и `KEY_FLG`) — залипания нет, но Shift сносится
|
||||
каждым overrun'ом (BUG-KBD-4).
|
||||
|
||||
Причина, по которой вариант 2 бил так часто, замерена: при зажатом Shift
|
||||
клавиатура обёртывает КАЖДЫЙ расширенный код служебной парой, поэтому тап
|
||||
стрелки — это не 5 байт, а 10, и overrun почти гарантирован.
|
||||
|
||||
**Что нашлось (замер в MAME, watchpoint на порт данных SIO + чтение карты
|
||||
`_kbdraw_down`).** Эта самая обёртка — «fake shift» — и есть решение, а не
|
||||
помеха. Клавиатура шлёт `E0 F0 12` перед расширенным make и `E0 12` после
|
||||
его break, и шлёт ТОЛЬКО пока Shift реально зажат. Проверено обоими
|
||||
шифтами: правый обёртывается своим кодом `0x59` (bit 1 байта 11 карты), левый
|
||||
— `0x12` (bit 2 байта 2). Это непрерывное и прямое свидетельство реального
|
||||
состояния Shift — единственное доступное, потому что опросить состояние у
|
||||
PS/2 нельзя, а typematic повторяет только ПОСЛЕДНЮЮ нажатую клавишу, то есть
|
||||
стрелку, а не Shift.
|
||||
|
||||
**Фикс.** Оба декодера (`libc/irq/_irq_tramp.c`, `libc/kbd/kbd_raw_poll.c`)
|
||||
читают обёртку в обе стороны:
|
||||
|
||||
- увидели fake shift → реальный Shift ЗАЖАТ → взвести plain-бит (бит кладёт
|
||||
общий писатель: достаточно обнулить префиксы, и он попадёт в plain-половину
|
||||
карты как make; в расширенную половину не пишем — это не клавиша);
|
||||
- расширенный make БЕЗ предшествующей обёртки → Shift ОТПУЩЕН → снять
|
||||
plain-биты обоих шифтов.
|
||||
|
||||
`kbd_raw_sync` вернулся к исключению модификаторов — теперь это безопасно:
|
||||
состояние Shift подтверждается независимо от того, что съел overrun, а
|
||||
залипание снимается первым же нажатием стрелки.
|
||||
|
||||
**Побочно найдена и исправлена своя ошибка** в новом коде `kbd_raw_poll`:
|
||||
после `bit 1, a` в `A` лежал `pending`, а не скан-код, — обычные
|
||||
(нерасширенные) клавиши декодировались бы из мусора. Оба декодера приведены
|
||||
к одной логике.
|
||||
|
||||
**Раскладка трамплина.** Клавиатурный блок перевалил за 127 байт, а `jp`
|
||||
внутри трамплина запрещён (код копируется в W2 побайтно, абсолютные
|
||||
само-ссылки сломают копию). Поэтому префиксные обработчики переехали вплотную
|
||||
к своим `cp`, а посередине тела стоят три ретранслятора (`tr_kbd_hub`,
|
||||
`tr_hub_notkbd`, `tr_hub_dss`) — до них дотягиваются `jr` и сверху, и снизу.
|
||||
В `kbd_raw_poll` такого ограничения нет, там три перехода стали `jp`.
|
||||
|
||||
**Проверено в MAME:**
|
||||
|
||||
- Shift зажат, пять тапов стрелки подряд → `LSh` остаётся `04` во всех пяти,
|
||||
`ovr = 0`, расширенная половина карты чистая;
|
||||
- штатное отпускание Shift → `LSh = 00`;
|
||||
- искусственно залипший Shift (бит записан в карту отладчиком) → снимается
|
||||
ПЕРВЫМ же тапом стрелки;
|
||||
- `make size-check`: 70 программ, роста нет.
|
||||
|
||||
<a id="bug-respawn-2"></a>
|
||||
## BUG-RESPAWN-2. После respawn стражи остаются мёртвыми — **ЗАКРЫТ**
|
||||
|
||||
**Вопрос из отчёта:** «после respawn — должны ли оживать стражники?»
|
||||
**Ответ по SDLPoP: да.** Цикл `play_level` (seg003:57) на КАЖДОЙ итерации —
|
||||
в том числе после смерти Кида — делает `load_level()`, следом `pos_guards()`
|
||||
(seg003:83), а затем `Guard.charid = charid_2_guard; Guard.direction =
|
||||
dir_56_none`. То есть стражи — такая же часть данных уровня, как тайлы, и
|
||||
перезагрузка файла возвращает их всех.
|
||||
|
||||
**Корень у нас.** `gstate_init()` (живая копия таблицы стражей: тайл,
|
||||
направление, мастерство, поза трупа) вызывался ТОЛЬКО при загрузке уровня.
|
||||
Рестарт возвращал тайлы (`pop_level_reset_tiles`), но не стражей: в `gstate`
|
||||
оставался сохранённый `leave_guard`'ом `seq_hi != 0`, и `pop_guard_enter`
|
||||
поднимал стража трупом с `guardhp_curr = 0`.
|
||||
|
||||
**Фикс.** `pop_level_reset_guards()` (тот же `gstate_init`) + `pop_guard_reset()`
|
||||
в `pop_start_level`, рядом с `pop_level_reset_tiles()`. Туда же уехал
|
||||
`pop_loose_mob_reset()` — настоящий `mobs_count = 0` из `start_level`.
|
||||
|
||||
**Проверено в MAME:** комната 21, страж жив (`alive = -1`, hp 3) → убит читом
|
||||
`K` (`alive = 0`, hp 0) → Кид убит стражем → респавн → возврат в комнату 21:
|
||||
страж снова `alive = -1`, hp 3.
|
||||
|
||||
Reference in New Issue
Block a user