Уровни 12 и 13: тень, Джафар, падающие плиты + разбор слоёв
Уровень 12 (тень) — порт seg002/seg006: - check_shadow: подъём тени в комнате 15 по условию «меч подобран», init_shad_12, вход падением (seq 7); - autocontrol_shadow_level12: ждать Кида, бой, сближение, СЛИЯНИЕ; - общий урон (ранил тень — ранил себя) и check_killed_shadow (убил тень — убил себя); - таймер вспышки слияния 42 -> -1, sword_disappears при уходе из комнаты 18, появление плит в комнатах 2/13 после слияния. Переход 12 -> 13 бесшовный: двери у уровня нет, он кончается фактом попадания в комнату 23 (tbl_seamless_exit); флаг pop_seamless не даёт сбросить HP. Чит навигации по комнатам этот триггер придерживает — иначе комнату 23 двенадцатого уровня не посмотреть в принципе. Уровень 13 (Джафар): guard_notice_timer (фора после встречи), on_guard_killed + Jaffar_exit, check_fall_flo (гряда плит сверху) и три исключения loose-механики. Плюс спрайты визиря VIZIER.DAT — без них он рисовался обычным стражем; заодно цвет из данных комнаты применяется только к обычному стражу, как в оригинале. Падающие плиты — семь дефектов, найденных прогонами: - MOB_MAX 4 -> 14: check_fall_flo роняет шесть плит разом, лишние терялись без щебня; - одиночные сигналы посадки/провала больше не затираются в кадре; - честный loose_fall: сбитая плита рождается в том же кадре, от места удара, с половинной скоростью; - при снятии плиты метится и сосед справа (висел передний торец); - heal и отрисовка кусков разнесены на два прохода; - куски рисуются ПОСЛЕ фона, порядок между собой — по убыванию y (compare_curr_objs для пары 0x80 сортирует наоборот); - wall_pattern убран из ceil_over_kid_tile: у оригинала узор из draw_tile_bottom идёт в фон, а не в передний слой. Хост-тесты: два новых набора, t_shadow (45 проверок) и t_jaffar (44). TK_ROOMS 8 -> 24 — спецсобытия адресуют комнаты по реальным номерам. Осознанные расхождения (docs/impl_diff.md): нет мигания Кида спрайтами тени при слиянии, чит навигации не запускает бесшовный переход, чит K убивает через штатный путь смерти. Открытым остаётся разбор слоёв: MOB-CLIP-RIGHT, MID-OVERLAY-LAYER, GUARD-FALLOUT-VICTORY, BG-ONCE (BUGS_OPEN.md / TASKS_OPEN.md). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
| ID | что | тип | статус |
|
||||
|----|-----|-----|--------|
|
||||
| [SPRITE-ZERO-W0](#sprite-zero-w0) | кадр персонажа изредка отдаёт спрайт 0x0 (залипание уже вылечено) | **редкий** | открыт: нужна трасса маппинга W0 |
|
||||
| [GATE-FORE-KID](#gate-fore-kid) | Кид в проёме ворот виден поверх решётки | окклюзия | открыт: портирован только шовный случай |
|
||||
| [GATE-FORE-KID](#gate-fore-kid) | Кид в проёме ворот виден поверх решётки | окклюзия | **фикс есть, ждёт проверки в MAME** (2026-08-13) |
|
||||
| [FORE-DUP](#fore-dup) | передний слой тайла рисуется дважды при перекрытии объектов | оптимизация | открыт: **сначала замерить**, потом чинить |
|
||||
| [DIED-ON-BUTTON](#died-on-button) | `died_on_button` (seg007:776) не портирован | порт | открыт |
|
||||
| [TORCH-ANIM-RIGHT](#torch-anim-right) | под запечённым пламенем застывают не только челюсти чомпера | **низкий** | открыт: на уровнях 1-4 такого соседства нет |
|
||||
@@ -23,6 +23,13 @@
|
||||
| [BUG-SPIKE-1](#bug-spike-1) | пики залипают выдвинутыми рядом с Кидом | **низкий** | маловоспроизводим: ни сценарием, ни попиксельной подгонкой X не поднимается |
|
||||
| [BUG-CHOMP-JUMP-1](#bug-chomp-jump-1) | прыжок с места вплотную к чомперу: кадр с отступом назад | **низкий** | маловоспроизводим: на повторе не поднялся; на пререлиз |
|
||||
| [GRAB-KBD-TIMING](#grab-kbd-timing) | зацеп в падении срабатывает менее стабильно, чем в оригинале | **после всех уровней** | открыт: подозрение на клавиатурный модуль, не на физику |
|
||||
| [GUARD-ENTRY-DEATH](#guard-entry-death) | вход в комнату вплотную к стражу = мгновенная смерть (не портирован `bump_into_opponent`) | порт | **фикс есть, ждёт проверки в MAME** |
|
||||
| [LOOSE-ROOM-CHANGE](#loose-room-change) | расшатанная плита не доваливается, если Кид ушёл в соседнюю комнату | порт | закрыт 2026-08-13, кроме [LOOSE-SEAM-ANIM](#loose-seam-anim) |
|
||||
| [JUMP-FLOOR-LEGS](#jump-floor-legs) | в прыжке с пола ноги Кида рисуются ПОВЕРХ кромки пола, а должны за ней | окклюзия | **корень найден и починен, ждёт проверки в MAME** |
|
||||
| [MOB-CLIP-RIGHT](#mob-clip-right) | окна и узор кладки рисуются ПОВЕРХ падающих плит | окклюзия | открыт: корень найден 2026-08-13 |
|
||||
| [MID-OVERLAY-LAYER](#mid-overlay-layer) | оверлей кромки всегда поверх плит и персонажей (у оригинала — midtable с сортировкой) | слои | открыт: нужен разбор архитектуры |
|
||||
| [GUARD-FALLOUT-VICTORY](#guard-fallout-victory) | страж, выпавший из комнаты, не засчитывается убитым | порт | открыт: две строки |
|
||||
| [LOOSE-SEAM-ANIM](#loose-seam-anim) | плита в шве (col −1), уроненная в прошлой комнате, исчезает без анимации падения | **мелкий** | открыт по решению пользователя: «пока пусть будет так» |
|
||||
|
||||
---
|
||||
|
||||
@@ -427,7 +434,31 @@ for (tile_row = x_top_row; tile_row <= char_bottom_row; ++tile_row)
|
||||
читать её из MAME. Симптом редкий (пользователь поймал дважды).
|
||||
|
||||
<a id="gate-fore-kid"></a>
|
||||
## GATE-FORE-KID. Кид, стоящий В ПРОЁМЕ ворот, виден ПОВЕРХ решётки
|
||||
## GATE-FORE-KID. Кид, стоящий В ПРОЁМЕ ворот, виден ПОВЕРХ решётки — ФИКС ЕСТЬ, ЖДЁТ ПРОВЕРКИ
|
||||
|
||||
> **Портировано 2026-08-13** (повтор нашёлся на уровне 10, комната 7, тайл
|
||||
> (2,6) — подтверждено чтением памяти MAME: `Kid.room=7, curr_row=2,
|
||||
> curr_col=6`, код тайла `0x04`). Сделано так:
|
||||
>
|
||||
> * **решение** — `pop_gate_over_char(ch)` в `pop_map.c`: персонаж стоит на
|
||||
> тайле ворот текущей комнаты (колонки 0..8; девятая отсекается, там бары
|
||||
> легли бы в соседнюю комнату — у оригинала это `Kid.room != room_R`);
|
||||
> * **отрисовка** — `draw_gate_fore` в `pop_bg.c`, точный порт seg008:1153:
|
||||
> только прозрачные куски 51 + ломти 52, без непрозрачного 50 и без
|
||||
> хвостового `DOOR_FRAM_SLICE` (фон под барами уже нарисован). Зовётся
|
||||
> ОДИН раз на персонажа за кадр, не в тайловом цикле;
|
||||
> * **тесты** — `tests-host/t_char.c`: `char_gate_covers_char_standing_in_it`,
|
||||
> `char_gate_ignores_neighbour_tiles`, `char_gate_ignores_other_room`,
|
||||
> `char_gate_never_in_last_column`. Отрисовка в харнесс не линкуется,
|
||||
> поэтому проверяется именно решение — ради этого оно и вынесено в карту.
|
||||
>
|
||||
> Расхождение с оригиналом (осознанное, в `docs/impl_diff.md`): оригинал
|
||||
> спрашивает жёстко про `Kid`, потому что foretable у него одна на проход
|
||||
> тайлов; у нас fore-проход идёт по персонажу, поэтому спрашиваем про того,
|
||||
> кого рисуем — страж в проёме тоже уходит за решётку.
|
||||
>
|
||||
> Шовный случай (ворота у СОСЕДА слева) работал и раньше — он ниже.
|
||||
> Банк 2 +601 Б, банк 3 +92 Б, резидент не изменился.
|
||||
|
||||
Нашёл пользователь 2026-08-11 (уровень 5, комната 24, верхние ворота):
|
||||
Кид стоит на тайле ворот, а решётка рисуется ПОД ним — он виден целиком,
|
||||
@@ -465,3 +496,319 @@ if (tile_left == tiles_4_gate && Kid.curr_row == drawn_row &&
|
||||
**Проверять:** уровень 5, комната 24 — встать в проём верхних ворот
|
||||
(колонка 1 ряда 0) при частично поднятой решётке; прутья должны
|
||||
перекрывать Кида. Тот же случай — любые ворота на уровнях 1-3.
|
||||
|
||||
---
|
||||
|
||||
<a id="guard-entry-death"></a>
|
||||
## GUARD-ENTRY-DEATH. Вход в комнату со стражем вплотную = мгновенная смерть — ФИКС ЕСТЬ, ЖДЁТ ПРОВЕРКИ
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-13).** В некоторых ситуациях (особенно
|
||||
после телепортов) Кид, входя в комнату, сразу оказывается чуть ли не в одном
|
||||
тайле со стражем и немедленно погибает: меч ещё в ножнах, а безоружному любой
|
||||
укол смертелен.
|
||||
|
||||
**Что в оригинале.** Стража при входе НЕ отбрасывает — проверено по SDLPoP:
|
||||
`enter_guard` (seg002:0112) ставит его ровно в тайл из данных уровня
|
||||
(`Char.x = level.guards_x[room-1]`), никаких проверок дистанции до Кида нет.
|
||||
Ситуация «вошёл вплотную» возможна и там. Защит четыре:
|
||||
|
||||
1. **`bump_into_opponent` (seg003:08AA)** — главная. Безоружный Кид
|
||||
(`Char.sword == sword_0_sheathed`) при вооружённом сопернике
|
||||
(`Opp.sword != sheathed`, `Opp.action < 2`), лицом к лицу
|
||||
(`Char.direction != Opp.direction`) и `can_guard_see_kid >= 2`, на
|
||||
`ABS(char_opp_dist()) <= 15` не получает укол, а ОТСКАКИВАЕТ: `Char.y`
|
||||
прижимается к полу, `fall_y = 0`, `seq_47_bump`.
|
||||
2. Кид сам достаёт меч — `control_standing` (seg005:0351).
|
||||
3. Страж поднимается в стойке покоя с мечом в ножнах — `seq_77`, ему нужно
|
||||
сперва заметить Кида (`autocontrol_guard_inactive` → `move_down_forw`).
|
||||
4. Вплотную страж не колет: `autocontrol_guard_active` при `distance < 12`
|
||||
(или `< 8` с убранным мечом) разворачивается/шагает, а `check_hurting`
|
||||
требует `min_hurt_range = 8` для безоружного противника.
|
||||
|
||||
**Корень у нас.** Пункты 2-4 были портированы верно (`pop_ctrl.c:288`,
|
||||
`pop_guard_cold.c:107`, `guards.c:553`), а **пункт 1 не портирован вовсе**:
|
||||
в `kid_phys` (`pop_map.c`) между `determine_col()` и `check_collisions()`
|
||||
было пусто, тогда как `play_kid_frame` (seg000:1238) зовёт там
|
||||
`bump_into_opponent`. Без отскока дистанция успевает вырасти в рабочий
|
||||
диапазон удара 8..29 — и первый же `move_6_shift` стража даёт
|
||||
`hurt_by_sword` с `take_hp(100)`.
|
||||
|
||||
**Сделано.** `bump_into_opponent` портирован в `pop_map.c` (рядом с
|
||||
`bumped_floor`) и вызывается из `kid_phys` на штатном месте. Резидент не
|
||||
вырос, банк 3: 11066 → 11156 Б. Опциональные фиксы SDLPoP
|
||||
(`fix_painless_fall_on_guard`, `fix_jumping_over_guard`) намеренно НЕ
|
||||
портированы — оставлено базовое поведение движка 1989 года.
|
||||
|
||||
**Проверять:** войти в комнату со стражем вплотную (телепортом `+`/`−` или
|
||||
`Shift+L`) без меча в руке — Кид должен отскочить с `seq_47`, а не умереть;
|
||||
падение на стража сверху по-прежнему безболезненно (это оригинал, не баг).
|
||||
|
||||
---
|
||||
|
||||
<a id="loose-room-change"></a>
|
||||
## LOOSE-ROOM-CHANGE. Расшатанная плита не доваливается, если Кид ушёл в соседнюю комнату — ФИКС ЕСТЬ, ЖДЁТ ПРОВЕРКИ
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-13).** Уровень 10, комната 2: в (2,9)
|
||||
стоит плита, которая под бегом Кида должна разрушаться. Если Кид пробегает
|
||||
по ней и уходит в правую комнату, плита не успевает разрушиться и упасть —
|
||||
переключение комнаты обрывает алгоритм.
|
||||
|
||||
**Что в оригинале.** loose-пол — обычный **trob** (`make_loose_fall`,
|
||||
seg007:0EF6 → `add_trob(curr_room, curr_tilepos, 0)`), а `process_trobs`
|
||||
(seg007:0000) крутит ВЕСЬ список каждый кадр, независимо от `drawn_room`:
|
||||
`animate_tile` делает `get_room_address(trob.room)` и работает с таблицей
|
||||
той комнаты, где trob живёт. Фаза лежит в `curr_room_modif`, то есть прямо
|
||||
в таблице уровня. Поэтому брошенная плита досчитывает до
|
||||
`loose_floor_delay`, `remove_loose` убирает тайл, `add_mob` рождает кусок —
|
||||
и всё это без Кида в комнате. Ровно так же ведут себя `mobs[]`
|
||||
(`do_mobs`, seg007:1063) — их список чистится только в `start_level`.
|
||||
|
||||
**Корень у нас.** Падающие КУСКИ (mob) чужую комнату умели давно
|
||||
(`pop_loose_mob_room_changed`, [BUG-LOOSE-2](BUGS_CLOSED.md)), а вот ФАЗА
|
||||
тряски/отсчёта жила в `pop_loose_modif[30]` — массиве ТЕКУЩЕЙ комнаты, —
|
||||
и `pop_loose_reset()` при смене комнаты обнулял его целиком. То есть
|
||||
плита, брошенная на полпути, возвращалась в покой. То же для плит-потолков
|
||||
(`pop_ceil_modif[10]`).
|
||||
|
||||
**Сделано** (двухуровневое хранение вместо переноса всей механики в trob —
|
||||
`pop_loose_modif` остаётся быстрым массивом отрисовки текущей комнаты):
|
||||
|
||||
1. `pop_loose_leave_room()` (`pop_map.c`, зовётся первым делом в
|
||||
`enter_room_side`, пока карта ещё описывает СТАРУЮ комнату) — сдаёт фазу
|
||||
всех loose-тайлов в персистентный `room_modif` (это и есть
|
||||
`curr_room_modif` оригинала) и заводит trob на незаконченные.
|
||||
2. `animate_loose()` (`pop_trob.c`, case `TILE_LOOSE` в `pop_process_trobs`)
|
||||
— досчитывает фазу заочно; на провале ставит тайл в EMPTY
|
||||
(`pop_level_set_tile`), кладёт в модификатор тип уровня (`remove_loose`)
|
||||
и рождает кусок через новый `pop_loose_mob_spawn_at(room, row, col)`.
|
||||
Пока комната ОТРИСОВАНА, trob снимается сразу — хозяин фазы
|
||||
`pop_loose_tick`.
|
||||
3. `pop_loose_reset()` теперь не обнуляет, а ЗАБИРАЕТ фазу новой комнаты из
|
||||
`room_modif`: вернувшийся Кид застаёт плиту на той же фазе.
|
||||
4. `pop_loose_forget()` — старт/рестарт уровня: забыть фазы БЕЗ сдачи
|
||||
(у оригинала всё сносит `load_level`). Без него первый же `enter_room`
|
||||
после `pop_trob_reset()` занёс бы в свежий уровень дрожь из прошлой жизни.
|
||||
|
||||
Резидент не изменился (20569 Б); банк 3 +451 Б, банк 6 +181 Б, банк 7 +62 Б.
|
||||
Хост-тест `phys_loose_survives_room_change` (`tests-host/t_phys.c`) закрывает
|
||||
передачу фазы.
|
||||
|
||||
**Проверять:** уровень 10, комната 2 — пробежать по плите (2,9) в правую
|
||||
комнату и вернуться: дыра должна быть на месте. Регресс: комната 7
|
||||
(две соседние плиты подряд), комната 12 плита (0,1) → уход в 15
|
||||
([BUG-LOOSE-2](BUGS_CLOSED.md)), плита-потолок, рестарт уровня после смерти
|
||||
на дрожащей плите.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<a id="loose-seam-anim"></a>
|
||||
## LOOSE-SEAM-ANIM. Плита в шве исчезает без анимации падения — МЕЛКИЙ, ОТЛОЖЕН
|
||||
|
||||
> **Решение пользователя (2026-08-13):** «пока пусть будет так».
|
||||
|
||||
**Наблюдение.** Уровень 10: Кид расшатывает плиту (2,9) комнаты 2 и убегает
|
||||
вправо в комнату 7, где та же плита видна в шве как (2,−1). Плита
|
||||
доваливается правильно (см. [LOOSE-ROOM-CHANGE](#loose-room-change)) и с
|
||||
экрана исчезает, но **мгновенно** — без дрожания и без падающего куска.
|
||||
|
||||
**Почему так.** Заочный досчёт (`animate_loose`, `pop_trob.c`) сознательно
|
||||
не рисует: комната не отрисована, а фаза живёт в `room_modif`, мимо
|
||||
`pop_loose_modif`, на который смотрит отрисовка дрожащего кадра. Исчезновение
|
||||
видно только потому, что провал меняет ТАЙЛ, а по этому событию
|
||||
(`pop_neigh_dirty`) главный цикл перечитывает кромку и перерисовывает шов.
|
||||
Падающий кусок (mob) рождается в комнате 2 и честно летит, но `mob_tick_one`
|
||||
рисует только `here` (своя комната), а комната 2 — не отрисованная.
|
||||
|
||||
**Что надо, если браться.** Шов — это НЕ обычный тайл: чужая колонка 9
|
||||
рисуется внутри нашей колонки 0 (`draw_tile`, ветка `lcode == 11`), а
|
||||
дрожащий кадр берётся из `pop_loose_modif[row*10 + (col-1)]`, то есть при
|
||||
`col == 0` индекс уезжает за границу массива (для ряда 0 — в −1). Значит
|
||||
анимация шва потребует не «включить рисование», а дать чужой колонке
|
||||
собственный источник фазы. Заодно там же чинить и этот выход за границу.
|
||||
|
||||
**Проверять:** тот же сценарий уровня 10; в оригинале плита в шве дрожит и
|
||||
роняет кусок так же, как в своей комнате.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<a id="jump-floor-legs"></a>
|
||||
## JUMP-FLOOR-LEGS. Прыжок с пола: ноги Кида поверх кромки пола — ПОЧИНЕН, ЖДЁТ ПРОВЕРКИ
|
||||
|
||||
> **Корень (найден трассой 2026-08-13).** Проход оверлеев в
|
||||
> `pop_fore_over_char` обходил **только два крайних ряда** футпринта:
|
||||
>
|
||||
> ```c
|
||||
> if (action != 2) other_overlay_tile(rB, c); /* опорный */
|
||||
> if (rT != rB) other_overlay_tile(rT, c); /* верхний */
|
||||
> ```
|
||||
>
|
||||
> Это буквальный порт `redraw_at_char2` (seg003:0645), и в оригинале он
|
||||
> верен: там `char_top_row` и `char_bottom_row` ВСЕГДА соседние. У нас между
|
||||
> ними появляется дыра — `char_footprint` форсит `rT = rB − 1` (голова торчит
|
||||
> в ряд выше), а затем окно fore-клипа расширяет `rB` ещё на ряд вниз.
|
||||
>
|
||||
> На спорном кадре трасса из MAME дала `rT=0 rB=2 cL=0 cR=1 do_ov=1` и **ноль**
|
||||
> вызовов `overlay_mid_tile`: ряды 0 и 2 обошли, ряд 1 — тот самый, где стоит
|
||||
> плита пола, — пропустили. Отсюда и подсказка пользователя «глубже в падении
|
||||
> плита рисуется правильно»: там `y_to_row` даёт уже 2, ряд 1 попадает в пару
|
||||
> крайних, и всё сходится.
|
||||
>
|
||||
> **Фикс:** обходить ВЕСЬ диапазон `rB..rT`, сохранив правило оригинала «при
|
||||
> `action == 2` опорный ряд не трогаем». После фикса трасса на том же кадре:
|
||||
> один вызов `overlay_mid_tile`, исход для (1,1) = «нарисовали», коды тайлов
|
||||
> `curr=3 left=0` — байт в байт как в отладочном выводе SDLPoP.
|
||||
>
|
||||
> Пилларный оверлей, который «работал и раньше», к этому отношения не имел:
|
||||
> это `fore_id = 95` из отдельного fore-прохода, а не midtable-оверлей.
|
||||
>
|
||||
> Банк 2 +28 Б. Хост-тесты (6 наборов) проходят.
|
||||
|
||||
### Разбор, по которому искали (оставлен: метод пригодится)
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-13).** Уровень 10, комната 1, прыжок с
|
||||
пола. В оригинале нижняя часть Кида скрыта ЗА передней гранью пола, у нас
|
||||
рисуется весь Кид поверх неё (за передней колонной он при этом уходит
|
||||
правильно). Скриншоты — в переписке сессии.
|
||||
|
||||
**Состояние кадра (снято из памяти MAME на замороженном кадре, сборка
|
||||
2026-08-13):**
|
||||
|
||||
| поле | значение |
|
||||
|------|----------|
|
||||
| `Kid.frame` | 103 (`frame_103_start_fall_2`) |
|
||||
| `Kid.action` | 3 (`actions_3_in_midair`) |
|
||||
| `Kid.x` / `Kid.y` | 69 / 127 |
|
||||
| `Kid.direction` | −1 (влево) |
|
||||
| `Kid.curr_col` / `curr_row` | 0 / 2 |
|
||||
| `Kid.room` / `cur_room` | 1 / 1 |
|
||||
|
||||
`pop_y_land[2] = 118` — то есть ноги на 9 px НИЖЕ уровня пола ряда 1, ровно в
|
||||
полосе его передней грани (`draw_tile_bottom` рисует её на `63*row + 65 = 128`).
|
||||
|
||||
Тайлы комнаты 1 (`room_fg`, прочитаны там же):
|
||||
|
||||
```
|
||||
ряд 0: 01 01 00 00 00 00 00 00 04 0B
|
||||
ряд 1: 04 00 03 13 10 11 13 01 07 0F
|
||||
ряд 2: 0C 01 03 01 01 0F 01 01 04 03
|
||||
```
|
||||
|
||||
Кромочная колонка левого соседа (`lcol_fg[0..2]`): `04 0C 04`.
|
||||
|
||||
**Что УЖЕ проверено и исключено.**
|
||||
|
||||
1. **Гейт `redraw_at_char2` (seg003:0645) портирован верно.** Сверено строка
|
||||
в строку: наш `do_ov` в `pop_fore_over_char` даёт то же множество поз
|
||||
(захват 78-79, `action` 2/3/4/6, `bumped` 102-106, начало подъёма 135-136),
|
||||
и climb-ветка 137..144 тоже совпадает. При `action == 3` оверлей
|
||||
ВКЛЮЧАЕТСЯ — дело не в гейте.
|
||||
2. **`draw_other_overlay` (seg008:1492) на колонке 0 не сработал бы и в
|
||||
оригинале.** Обе его ветки требуют пустого соседа: `tile_left == empty`
|
||||
(у нас `lcol_fg[1] = 0x0C`, doortop — не пусто) либо `drawn_col > 0` и
|
||||
пустой тайл ЧЕРЕЗ ОДИН слева. Значит окклюдер — НЕ он, и наш порт этой
|
||||
функции здесь ни при чём.
|
||||
3. **`pop_tile_code(row, −1)` кромку читает правильно** — из `pop_t_lfg`, а не
|
||||
«стена всегда».
|
||||
|
||||
**Две живые гипотезы, проверять в этом порядке.**
|
||||
|
||||
* **Это вообще не отрисовка, а физика.** У нас `Kid.y = 127` при уровне пола
|
||||
118 — Кид «утоплен» на 9 px, и ноги торчат ниже кромки просто потому, что
|
||||
они там и есть. Проверяется дёшево: снять `Char.y` у SDLPoP на том же
|
||||
кадре 103 той же последовательности. Если там 118..120 — искать надо в
|
||||
seqtbl/`fall_speed`, а не в слое фона.
|
||||
* **Окклюдер — другой тайл/другой механизм.** Тогда нужна очная ставка:
|
||||
прогнать SDLPoP с трассой (`dbg_shots`-fprintf в `draw_other_overlay` и
|
||||
`add_kid_to_objtable` уже стоят в нашей копии, см. memory
|
||||
`pop_pixel_diff_vs_sdlpop`) и сравнить СПИСОК тайлов, попавших в midtable
|
||||
на этом кадре, с тем, что рисуем мы. Гадать дальше по коду смысла нет —
|
||||
два прохода по seg008 уже не дали ответа.
|
||||
|
||||
**Проверять после починки:** тот же прыжок в комнате 1 уровня 10; за передней
|
||||
колонной Кид уходит и сейчас — это не должно сломаться.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<a id="mob-clip-right"></a>
|
||||
## MOB-CLIP-RIGHT. Окна и узор кладки рисуются поверх падающих плит
|
||||
|
||||
**Найдено прогонами 2026-08-13 (уровень 13, комнаты 16 и 23).**
|
||||
|
||||
**Симптом.** На падающей плите проступают элементы ЗАДНЕЙ стены — узор
|
||||
кладки, а на ряде 0 и целое окно. Плита при этом цела: элементы ложатся
|
||||
поверх неё, не стирая.
|
||||
|
||||
**Корень.** `mob_render` (`pop_room.c`) каждый кадр вызывает
|
||||
`draw_tile(r, m->col + 1)` — перерисовывает ЦЕЛЫЙ соседний тайл поверх уже
|
||||
нарисованного куска, чтобы спрятать его правую часть (спрайт 42 лежит в
|
||||
столбце `col+1`). `draw_tile` кладёт всё содержимое тайла, включая декор
|
||||
пустого тайла — окно и узор. Ряд берётся из координаты куска, поэтому на
|
||||
ряде 0 перерисовывается тайл с окном.
|
||||
|
||||
Правка [#6](#) (клип перерисовки окном коридора heal) сделала артефакт
|
||||
ЗАМЕТНЕЕ: раньше перерисовка размазывалась вокруг, теперь попадает точно в
|
||||
прямоугольник плиты.
|
||||
|
||||
**Как в оригинале.** Такой перерисовки нет вовсе. `redraw_at_cur_mob`
|
||||
(seg007:1094) зовётся ТОЛЬКО из `loose_fall`, один раз в момент сбития, и
|
||||
лишь ставит пометки на тайл и соседа. Правая часть куска прячется
|
||||
собственным клипом объекта: `add_mob_to_objtable` (seg007:1161) задаёт
|
||||
`clip.right = 40`.
|
||||
|
||||
**Что делать.** Портировать клип вместо подпорки:
|
||||
|
||||
1. выяснить, во что превращается `clip.right = 40` в наших координатах
|
||||
(у оригинала `obj_x` в удвоенных единицах: `char_x_left = obj_x / 2 + 58`);
|
||||
2. блитить правую часть (env 42) с ограничением ширины — примитив с шириной
|
||||
есть для персонажей (`gfx_blit_cols_part_w`), для env надо посмотреть;
|
||||
3. убрать `draw_tile(r, col+1)` и окно fore-клипа вокруг него;
|
||||
4. вернуть коридор heal к 64x32.
|
||||
|
||||
**Побочная выгода:** уходит по одному полному `draw_tile` на кусок за кадр —
|
||||
при шести падающих плитах это шесть отрисовок тайла.
|
||||
|
||||
---
|
||||
|
||||
<a id="mid-overlay-layer"></a>
|
||||
## MID-OVERLAY-LAYER. Оверлей кромки всегда поверх, а должен сортироваться
|
||||
|
||||
**Найдено разбором 2026-08-13.**
|
||||
|
||||
`overlay_mid_tile` (`pop_bg.c`) выполняется в проходе ПОСЛЕ персонажей, то
|
||||
есть безусловно поверх всего нарисованного. В оригинале это `draw_other_overlay`
|
||||
(seg008:1499), и он переключает таблицу на **midtable** —
|
||||
тот же слой, где живут персонажи и падающие куски (`add_mob_to_objtable`,
|
||||
тип `0x80`), а внутри слоя всё сортируется по `y` (`compare_curr_objs`,
|
||||
seg008:1572).
|
||||
|
||||
Следствие: узор из оверлея выигрывает у плиты, даже когда плита ближе к
|
||||
зрителю. Пока у нас нет сортируемого midtable, точечными правками это не
|
||||
лечится.
|
||||
|
||||
Связано с [BG-ONCE](TASKS_OPEN.md#bg-once) — там та же техническая
|
||||
сердцевина: привести наши слои в соответствие с таблицами оригинала.
|
||||
|
||||
---
|
||||
|
||||
<a id="guard-fallout-victory"></a>
|
||||
## GUARD-FALLOUT-VICTORY. Выпавший из комнаты страж не засчитывается убитым
|
||||
|
||||
**Найдено разбором 2026-08-13** (при проверке чита `K` на Джафаре).
|
||||
|
||||
У `on_guard_killed` в оригинале ДВА места вызова:
|
||||
|
||||
| место | что |
|
||||
|---|---|
|
||||
| `play_guard`, seg006:1494 | HP кончились — **портировано** |
|
||||
| `check_guard_fallout`, seg002:264 | страж выпал из комнаты — **НЕ портировано** |
|
||||
|
||||
Наш `pop_guard_fallout` (`pop_guard.c`) просто гасит слот. Значит столкнуть
|
||||
Джафара в пропасть на 13-м уровне можно, а выход на 14-й от этого не
|
||||
откроется — расхождение с оригиналом.
|
||||
|
||||
Правка: позвать `on_guard_killed()` в ветке «не скелет». Мешает только то,
|
||||
что функция сейчас `static` в `guards.c` (банк 1), а `pop_guard_fallout`
|
||||
живёт в резиденте.
|
||||
|
||||
Reference in New Issue
Block a user