L2: падение с мечом, разбег-прыжок через 3 тайла, стартовое состояние ворот
BUG-FALL-SWORD-1 — start_fall (seg006:1044) был портирован не целиком: не хватало трёх веток выбора последовательности и уборки меча в ножны. Из-за seq_7 с set_fall(1,15) (дрейф 1 px/кадр) Кид с мечом уезжал примерно на тайл вбок; в оригинале это seq_81_fightfall — падение строго вниз. Сверено по логу SDLPoP: кадры 102..105 дают x = 155,157,159,160. BUG-RJUMP-1 — run_jump (seg005:0AA8) не выравнивал Кида по кромке пола перед толчком, а был заглушкой «полировка K3». Суммарный dx seq_4 — 62 px при тайле 14, то есть провал ровно в три тайла берётся ТОЛЬКО с кромки: без выравнивания Кид не перепрыгивал его никогда. Порт — pop_run_jump_align() в pop_map (беззнаковое сравнение оригинала = «сдвиг не попал в [-8,-1]»). На харнессе: было — толчок с x=165, кадр 44 в колонке 3 (провал); стало — 5 кадров добега, толчок с x=149, кадр 44 даёт x=87 col=1 row=1. Остальные 8 сценариев не изменились. BUG-GATEMOD-1 — load_alter_mod (seg008:198E) был портирован только для зелий, поэтому ворота с bg=1 («Open» по спецификации DAT, табл. 8) стартовали закрытыми. Добавлены ветки gate (1 -> 188) и loose. Ветка wall намеренно НЕ портируется: связи стен наш pop_bg считает по типам соседей в момент отрисовки. На уровнях 1-3 таких ворот всего двое (ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5)) — у остальных bg=2, а 2 и 0 ведут себя одинаково. Убран временный трассировщик pop_dbg_trace/pop_dbg_draw; pop_dbg_trap() оставлен как многоразовый инструмент. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@
|
|||||||
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||||
| — | [L1-PASS](#l1-pass) | сквозной прогон ур. 1 + таблица 24 комнат | приёмка ур. 1 |
|
| — | [L1-PASS](#l1-pass) | сквозной прогон ур. 1 + таблица 24 комнат | приёмка ур. 1 |
|
||||||
| — | [DBG-CHEATS](#dbg-cheats) | `[`/`]` — подгонка Кида по X | отладка (BUG-GATE-PASS-1) |
|
| — | [DBG-CHEATS](#dbg-cheats) | `[`/`]` — подгонка Кида по X | отладка (BUG-GATE-PASS-1) |
|
||||||
|
| — | [MEM-BANK5](#mem-bank5) | новый банк кода: W1/W2 осталось 38 Б кучи | берётся по факту нехватки места |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -154,6 +155,17 @@ loose-плиту (комната 7, колонка 4, ряд 0). Механик
|
|||||||
|
|
||||||
### KBD-1. Shift + стрелки: нажатия теряются — **ПРИЧИНА НАЙДЕНА, ФИКС ПРОВЕРЕН**
|
### KBD-1. Shift + стрелки: нажатия теряются — **ПРИЧИНА НАЙДЕНА, ФИКС ПРОВЕРЕН**
|
||||||
|
|
||||||
|
> **ПОПРАВКА К ПОСЫЛКЕ (2026-08-05).** Ниже «fake shift» подан как
|
||||||
|
> установленный факт («при зажатом Shift PS/2 удваивает трафик»). Прямой
|
||||||
|
> замер потока байт это не подтвердил: клавиатура MAME-Sprinter
|
||||||
|
> (`pc_kbd ms_naturl`) обёртку `E0 F0 12` / `E0 12` не шлёт вовсе — при
|
||||||
|
> зажатом Shift поток на стрелку ровно `E0 75 E0 75 …`. Значит удвоения
|
||||||
|
> трафика в связке Shift+стрелка нет, и мотивировка «поэтому FIFO
|
||||||
|
> переполняется» отпадает; сам ФИКС (плотный опрос `kbd_raw_poll`) остаётся
|
||||||
|
> верным и нужным — переполнение вызывает не Shift, а короткая жизнь
|
||||||
|
> импульса IRQ (пункт 3 гипотезы) плюс DI-окна графики. Разбор и следствия
|
||||||
|
> — [BUG-KBD-5](bug_closed.md#bug-kbd-5).
|
||||||
|
>
|
||||||
> **Итог (2026-08-01).** Причина — не наш код и не DI-окна графики: при
|
> **Итог (2026-08-01).** Причина — не наш код и не DI-окна графики: при
|
||||||
> зажатом Shift PS/2 удваивает трафик («fake shift»), а импульс запроса
|
> зажатом Shift PS/2 удваивает трафик («fake shift»), а импульс запроса
|
||||||
> прерывания здесь теряется примерно в 44 % случаев, и трёхбайтовый FIFO
|
> прерывания здесь теряется примерно в 44 % случаев, и трёхбайтовый FIFO
|
||||||
@@ -659,6 +671,47 @@ Shift+L → уровень 3 (комната 9). То есть цепочка
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### <a id="mem-bank5"></a>MEM-BANK5. Разгрузка W1/W2 новым банком кода
|
||||||
|
|
||||||
|
Вопрос 2026-08-04: «надо делать новый банк?». **Да, и он лечит именно то,
|
||||||
|
что жмёт.** В нашей раскладке (`MEMORY=huge`, small-вариант) CODE и DATA
|
||||||
|
живут в ОДНОМ 32-КБ пространстве W1+W2 — карта текущей сборки:
|
||||||
|
|
||||||
|
```
|
||||||
|
_CODE 0x4100..0xAA00 26880 Б
|
||||||
|
_DATA 0xAAD0..0xB9B0 3808 Б
|
||||||
|
_BSS 0xB9B8..0xBADA 290 Б
|
||||||
|
куча 0xBADA..0xBB00 38 Б ← упёрлись сюда, добавляя отладку
|
||||||
|
стек 0xBB00..0xC000 1280 Б
|
||||||
|
```
|
||||||
|
|
||||||
|
Поэтому **каждый килобайт кода, уехавший в банк, становится килобайтом,
|
||||||
|
доступным данным**. Отдельного «дефицита W2» у нас нет — дефицит один.
|
||||||
|
(38 байт кучи не опасны сами по себе: malloc'ом мы не пользуемся, страницы
|
||||||
|
берутся через `mem_alloc_block`. Опасно то, что следующая структура
|
||||||
|
данных упрётся в стек молча.)
|
||||||
|
|
||||||
|
Резидентный код по модулям (из `.sprinter-cc-roomtest/*.rel`):
|
||||||
|
|
||||||
|
| модуль | _CODE | как часто зовётся | в банк? |
|
||||||
|
|--------|------:|-------------------|---------|
|
||||||
|
| `pop_kid.c` | 6287 | `load_frame`/`play_seq` — 2×/кадр (Кид + страж) | частично: холодная половина (загрузка страниц спрайтов, `pop_kid_load`) — да; движок кадров — нет |
|
||||||
|
| `roomtest.c` | 3893 | main-loop | нет (точка входа, зовёт всех) |
|
||||||
|
| `pop_trob.c` | 2526 | `do_trobs` 1×/кадр, но `pop_trob_modif` — горячий аксессор | **кандидат №1**, если вынести аксессор в резидент |
|
||||||
|
| `pop_level.c`| 2292 | `pop_level_tile` — из `pop_bg` (банк 2) на каждый тайл | **нет**: банк→банк на каждый тайл убьёт отрисовку |
|
||||||
|
| `pop_ctrl.c` | 2189 | `user_control` 1×/кадр | **кандидат №2** — дёшево и безопасно |
|
||||||
|
| `pop_guard.c`| 923 | 1×/кадр | нет смысла |
|
||||||
|
|
||||||
|
Порядок действий, когда упрёмся: `pop_ctrl.c` → банк 5 (2.2 КБ, один
|
||||||
|
banked-вызов за кадр), затем расщепление `pop_kid.c` на горячее ядро и
|
||||||
|
холодную загрузку. Критерий кандидата — **не размер, а частота вызова и
|
||||||
|
отсутствие горячих банк→банк переходов**; `pop_level` показывает, что
|
||||||
|
большой холодный на вид модуль может быть горячим аксессором.
|
||||||
|
|
||||||
|
Брать по факту нехватки места, не заранее.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Отложено осознанно (не брать, пока не появится причина)
|
## Отложено осознанно (не брать, пока не появится причина)
|
||||||
|
|
||||||
- **Звук** (CBL-эффекты, Фаза 5 `PORT_PLAN.md`) — геймплей не блокирует.
|
- **Звук** (CBL-эффекты, Фаза 5 `PORT_PLAN.md`) — геймплей не блокирует.
|
||||||
|
|||||||
@@ -11,6 +11,151 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## BUG-RJUMP-1. Разбег-прыжок не берёт провал в три тайла — **ЗАКРЫТ 2026-08-04**
|
||||||
|
|
||||||
|
**Симптом (пользователь, приёмка уровня 2).** Комната 1: Кид с разбегу
|
||||||
|
обязан перелететь колодец с (0,5) на (0,1) — у нас он долетает до колодца и
|
||||||
|
валится вертикально вниз. Комната 9: то же с (1,5) на (1,1). Обобщение
|
||||||
|
пользователя оказалось точным: **провал ровно в три пустых тайла наш Кид не
|
||||||
|
перепрыгивал никогда**, а провалы поменьше брал.
|
||||||
|
|
||||||
|
**Почему «никогда», а не «иногда».** Суммарный `dx` последовательности
|
||||||
|
`seq_4_run_jump` (кадры 34..44) — 62 пикселя при ширине тайла 14. Это
|
||||||
|
ровно 4 колонки и меньше половины тайла запаса. То есть перелёт трёх
|
||||||
|
пустых тайлов возможен ТОЛЬКО если оттолкнуться почти точно от кромки; из
|
||||||
|
случайной фазы бегового цикла он не получается никогда.
|
||||||
|
|
||||||
|
**Корень.** Оригинал именно поэтому и не даёт прыгать откуда попало:
|
||||||
|
`run_jump` (seg005:0AA8) перед стартом **выравнивает Кида по кромке**.
|
||||||
|
|
||||||
|
```c
|
||||||
|
short xpos = char_dx_forward(4);
|
||||||
|
short col = get_tile_div_mod_m7(xpos);
|
||||||
|
for (short tiles_forward = 0; tiles_forward < 2; ++tiles_forward) {
|
||||||
|
col += dir_front[Char.direction + 1];
|
||||||
|
get_tile(Char.room, col, Char.curr_row);
|
||||||
|
if (curr_tile2 == tiles_2_spike || !tile_is_floor(curr_tile2)) {
|
||||||
|
pos_adjustment = distance_to_edge(xpos) + TILE_SIZEX * tiles_forward - TILE_SIZEX;
|
||||||
|
if ((word)pos_adjustment < (word)-8 || pos_adjustment >= 2) {
|
||||||
|
if (pos_adjustment < 128) return; // ПРЫЖКА НЕТ
|
||||||
|
pos_adjustment = -3;
|
||||||
|
}
|
||||||
|
Char.x = char_dx_forward(pos_adjustment + 4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
control_up = release_arrows();
|
||||||
|
seqtbl_offset_char(seq_4_run_jump);
|
||||||
|
```
|
||||||
|
|
||||||
|
**У нас этой половины не было** — стояла заглушка с честным комментарием
|
||||||
|
«оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это полировка
|
||||||
|
K3; K2b просто запускает run-jump». Полировкой это не оказалось: без
|
||||||
|
выравнивания три тайла непроходимы в принципе.
|
||||||
|
|
||||||
|
**Две тонкости, которые легко потерять при порте.**
|
||||||
|
|
||||||
|
1. **Беззнаковое сравнение.** `(word)pos_adjustment < (word)-8 || pos_adjustment >= 2`
|
||||||
|
означает ровно «`pos_adjustment` НЕ попал в `[-8,-1]`». Ветка
|
||||||
|
`pos_adjustment = -3` недостижима: `distance_to_edge ∈ [0,13]`,
|
||||||
|
`tiles_forward ∈ {0,1}`, значит `pos_adjustment ∈ [-14,13]`, а туда нужно
|
||||||
|
`>= 128`. В порте она записана как мёртвая, с объяснением.
|
||||||
|
2. **Отказ НЕ гасит `control_up`.** `return` выходит из `run_jump` до
|
||||||
|
`release_arrows()`, поэтому Кид бежит дальше с зажатой «вверх» и пробует
|
||||||
|
снова на следующем кадре. Именно так игрок и ловит фазу — просто
|
||||||
|
удерживая клавишу. Если погасить, прыжок у кромки станет одноразовым и
|
||||||
|
почти всегда неудачным.
|
||||||
|
|
||||||
|
**Фикс.** Тайловая половина — `pop_run_jump_align()` в `pop_map` (там живут
|
||||||
|
`get_tile`/`distance_to_edge`), диспетчерская — в `pop_ctrl.run_jump`.
|
||||||
|
Разделение то же, что у `pop_jump_up_seq`: pop_map правит `Kid.x` напрямую,
|
||||||
|
и `pop_savekid_state` эту правку намеренно не затирает.
|
||||||
|
|
||||||
|
**Проверка — на харнессе, а не в MAME.** Сценарий
|
||||||
|
`phys_running_jump_over_3tile_gap` в [`tests-host/t_phys.c`](tests-host/t_phys.c)
|
||||||
|
(комната с провалом в колонках 2–4). Было: прыжок со старта в кадре 34 при
|
||||||
|
`x=165`, кадр 44 приходится на колонку 3 — провал, падение. Стало: Кид
|
||||||
|
пробегает лишние 5 кадров, выравниватель ловит фазу, прыжок стартует при
|
||||||
|
`x=149`, кадр 44 даёт `x=87, col=1, row=1` — приземление на пол и бег
|
||||||
|
дальше. Остальные 8 сценариев набора не изменились ни на байт: правка
|
||||||
|
трогает только ветку разбег-прыжка у кромки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BUG-FALL-SWORD-1. Отход с мечом в провал: не та последовательность падения — **ЗАКРЫТ 2026-08-04**
|
||||||
|
|
||||||
|
**Симптом (приёмка уровня 2, комната 4).** Кид с вынутым мечом отступает
|
||||||
|
от стража к дыре от упавших loose-плит. Наблюдение пользователя: он
|
||||||
|
«проваливается раньше времени», летит **с клинком в руке**, и падает **по
|
||||||
|
другим X**, чем в оригинале — «почти на целый тайл левее».
|
||||||
|
|
||||||
|
**Разбор.** Сверка `seg006:1044 start_fall` показала, что наш порт
|
||||||
|
пропустил ТРИ вещи из оригинала, и все три бьют именно по этому сценарию:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void start_fall() {
|
||||||
|
Char.sword = sword_0_sheathed; // (1) меч В НОЖНЫ
|
||||||
|
inc_curr_row(); start_chompers();
|
||||||
|
...
|
||||||
|
} else if (frame >= 81 && frame < 86) { // (2) срыв при приземлении
|
||||||
|
seq_id = seq_19_fall; // после прыжка вверх
|
||||||
|
Char.x = char_dx_forward(5);
|
||||||
|
load_fram_det_col();
|
||||||
|
} else if (frame >= 150 && frame < 180) { // (3) кадры С МЕЧОМ
|
||||||
|
droppedout = 1;
|
||||||
|
if (Char.direction < dir_0_right && distance_to_edge_weight() <= 7)
|
||||||
|
Char.x = char_dx_forward(-5);
|
||||||
|
seq_id = seq_81_kid_pushed_off_ledge;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
У нас все они падали в общий `else` → `seq_7` (stepfall). Разница между
|
||||||
|
`seq_7` и `seq_81` в `seqtbl.c` и объясняет ВЕСЬ симптом:
|
||||||
|
|
||||||
|
```
|
||||||
|
seq_7 stepfall : dx(1) dy(3) | 102 | dx(2) dy(6) | dx(-1) dy(9) | dy(12) | dx(-2) set_fall(1,15)
|
||||||
|
seq_81 fightfall : dy(-1)| 102 | dx(-2) dy(6)| dx(-2) dy(9) | dx(-1) dy(12) | dx(-3) set_fall(0,15)
|
||||||
|
```
|
||||||
|
|
||||||
|
`set_fall(1, 15)` против `set_fall(0, 15)` — **горизонтальный дрейф**: в
|
||||||
|
`seq_7` во время всего свободного падения `Char.x` уходит на 1 в сторону
|
||||||
|
КАЖДЫЙ кадр. За два этажа падения это и есть тот самый «почти тайл».
|
||||||
|
Оригинал в бою падает строго вниз.
|
||||||
|
|
||||||
|
**Сверка по числам** (лог SDLPoP `DBG shot`, кадры 102..105):
|
||||||
|
|
||||||
|
```
|
||||||
|
SDLPoP: 102 x=155 103 x=157 104 x=159 105 x=160 ← +2 +2 +1 = seq_81
|
||||||
|
у нас: 102 x=151 ← seq_7
|
||||||
|
```
|
||||||
|
|
||||||
|
Обратный счёт: у SDLPoP в момент решения `x = 150`, дальше
|
||||||
|
`char_dx_forward(-5)` при `dir=-1` даёт `+5` → 155. У нас решение при
|
||||||
|
`x = 152` — то есть **по самому правилу срыва расхождения нет**: обе
|
||||||
|
позиции лежат в колонке 7 (`dx_weight = x + 14`, кадр 157 имеет
|
||||||
|
`weight_x = 14`; колонка 7 — это `x ∈ [149,163)`). Двухпиксельная разница
|
||||||
|
— фаза отхода (шаг отступления `dx(-3)+dx(-2)` = 5 пикселей за цикл), а она
|
||||||
|
зависит от RNG стража и между движками совпасть не обязана. «Раньше
|
||||||
|
времени» — это не срыв не там, а seq_7 вместо seq_81.
|
||||||
|
|
||||||
|
**Что подтвердилось попутно.** Старт уровня 2 у нас **байт в байт** как в
|
||||||
|
SDLPoP: `frame=15 x=107 y=118 dir=-1 col=3 row=1 room=5`. Таблицы кадров и
|
||||||
|
`seqtbl` вынуты из оригинального бинарника, так что расходиться могут
|
||||||
|
только РЕШЕНИЯ движка — искать надо всегда там.
|
||||||
|
|
||||||
|
**Связь с [BUG-LAND-SWORD-1](#bug-land-sword-1).** Тот фикс (`land()`
|
||||||
|
даёт `seq_63` при вынутом мече) остаётся — он есть в оригинале, — но для
|
||||||
|
Кида он теперь почти недостижим: `start_fall` убирает меч в ножны, и после
|
||||||
|
приземления кадр 109 разбирает обычный `control_crouched`. То есть
|
||||||
|
настоящий корень вечного приседа был здесь, а не в `land()`.
|
||||||
|
|
||||||
|
**Метод.** Пустышка `pop_dbg_trap()` в резиденте W1 (идея пользователя):
|
||||||
|
брейкпоинт на банковый код ставить нельзя — 0xC000+ это окно, куда мапятся
|
||||||
|
все банки, и точка ловит чужие функции. Оставлена в `pop_state.c` как
|
||||||
|
многоразовый инструмент.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## BUG-CTRL-FRAME-1. Геометрия Кида считалась по кадру СТРАЖА — **ЗАКРЫТ 2026-08-04**
|
## BUG-CTRL-FRAME-1. Геометрия Кида считалась по кадру СТРАЖА — **ЗАКРЫТ 2026-08-04**
|
||||||
|
|
||||||
Самый неприятный класс: не косметика, а неверные РЕШЕНИЯ движка, причём
|
Самый неприятный класс: не косметика, а неверные РЕШЕНИЯ движка, причём
|
||||||
@@ -523,13 +668,15 @@ MAME по кромкам.
|
|||||||
|
|
||||||
## НЕ БАГИ (кривая картинка, но совпадает с оригиналом — НЕ чинить)
|
## НЕ БАГИ (кривая картинка, но совпадает с оригиналом — НЕ чинить)
|
||||||
|
|
||||||
- **Отход с ВЫНУТЫМ МЕЧОМ роняет Кида в яму «раньше, чем кажется»** (комната 4
|
- **Кадр падения с мечом: голова/руки поверх кромки пола — САМ РЕНДЕР верен.**
|
||||||
уровня 2, кромка ряда 1 у дыры от упавшей loose-плиты). Сверено с SDLPoP
|
Сверено с SDLPoP v1.24 (2026-08-04): там кадр падения выглядит один в один.
|
||||||
v1.24 пользователем (2026-08-04): оригинал падает **с той же позиции, по той
|
⚠ Речь ТОЛЬКО о виде кадра. Расхождение по X, которое было отмечено здесь
|
||||||
же траектории и с тем же видом кадра падения** (голова/руки поверх кромки
|
как открытое, разобрано и закрыто: [BUG-FALL-SWORD-1](#bug-fall-sword-1) —
|
||||||
пола). Наш кадр и кадр SDLPoP совпадают один в один.
|
дело не в моменте срыва (он верен), а в том, что мы играли `seq_7` вместо
|
||||||
|
`seq_81` и получали горизонтальный дрейф `set_fall(1,15)` за всё падение.
|
||||||
|
|
||||||
Механика, чтобы не разбирать заново. У стоек с мечом ОГРОМНАЯ точка веса:
|
Механика точки веса, чтобы не разбирать заново. У стоек с мечом она
|
||||||
|
ОГРОМНАЯ:
|
||||||
|
|
||||||
```
|
```
|
||||||
кадр 158/170/171 (stand_with_sword): dx=0 weight_x=13
|
кадр 158/170/171 (stand_with_sword): dx=0 weight_x=13
|
||||||
@@ -745,8 +892,105 @@ foretable — в той же странице по смещению `0x1000` (с
|
|||||||
|
|
||||||
# Вторая волна прогона 2026-08-03 (вечер)
|
# Вторая волна прогона 2026-08-03 (вечер)
|
||||||
|
|
||||||
|
<a id="bug-kbd-5"></a>
|
||||||
|
## BUG-KBD-5. Зажатый Shift снимается автоповтором стрелки — **ЗАКРЫТ 2026-08-05**
|
||||||
|
|
||||||
|
**Симптом (пользователь).** Прыжок с места с зацепом (уровень 2, комната 9)
|
||||||
|
не выходит: Кид прыгает, но за кромку не цепляется. Дальше уточнения,
|
||||||
|
которые и указали на клавиатуру, а не на физику:
|
||||||
|
|
||||||
|
- «отпускаю стрелки в полёте и жму Shift один — зацеп есть»;
|
||||||
|
- «жму Shift не сразу, а когда бо́льшая часть прыжка позади — зацеп есть»;
|
||||||
|
- «зажимаю Shift, потом стрелки — прыжок есть, зацепа нет»;
|
||||||
|
- в SDLPoP та же комбинация в том же порядке работает всегда.
|
||||||
|
|
||||||
|
Обобщение: **Shift работал в одиночку и не работал вместе со стрелками.**
|
||||||
|
Ровно то же ломало и осторожный шаг — игрок держит Shift, тапает стрелку, а
|
||||||
|
Кид на каком-то тапе уходит в бег (это же поведение раньше описывалось как
|
||||||
|
BUG-KBD-4, см. ниже).
|
||||||
|
|
||||||
|
**Замер (MAME, `roomtest`, чтение карты `_kbdraw_down` + breakpoint на выходе
|
||||||
|
из `in a,($18)` в декодере трамплина).**
|
||||||
|
|
||||||
|
1. Зажать LShift → байт 2 карты = `04` (LSh взведён), поток `12 12 12 …`
|
||||||
|
(Shift автоповторяется сам, пока он последняя нажатая клавиша).
|
||||||
|
2. Добавить ↑ → байт 46 = `20` (↑ взведена), **байт 2 = `00` — Shift снят,
|
||||||
|
хотя физически зажат.**
|
||||||
|
3. Добавить ещё → байт 46 = `30` (обе стрелки видны, 3-key rollover в
|
||||||
|
порядке), Shift по-прежнему `00`.
|
||||||
|
4. КОРОТКИЙ тап ↑ при зажатом Shift — Shift выживает. Это и сбивало с
|
||||||
|
толку: бит сносился, но тут же восстанавливался, потому что после
|
||||||
|
отпускания стрелки Shift снова становился «последней клавишей» и его
|
||||||
|
автоповтор `12` взводил бит обратно за ~30 мс.
|
||||||
|
5. Сам поток при зажатом Shift и зажатой ↑:
|
||||||
|
|
||||||
|
```
|
||||||
|
… E0 E0 E0 75 E0 75 E0 75 ← ни одного F0/12
|
||||||
|
```
|
||||||
|
|
||||||
|
**Причина.** Декодеры (`_irq_tramp.c`, `kbd_raw_poll.c`) делали из «fake
|
||||||
|
shift» ДВА вывода, и второй был неверен:
|
||||||
|
|
||||||
|
- обёртка `E0 F0 12` / `E0 12` есть → Shift зажат → взвести бит ✔ верно;
|
||||||
|
- расширенный make **без** обёртки → Shift отпущен → снять биты обоих
|
||||||
|
шифтов ✘ **неверно**.
|
||||||
|
|
||||||
|
Обратный вывод опирался на «клавиатура обёртывает КАЖДЫЙ расширенный код».
|
||||||
|
Замер это опровергает: клавиатура MAME-Sprinter (`pc_kbd ms_naturl`) обёртку
|
||||||
|
не шлёт вовсе, и уж точно её не бывает на typematic-повторах — а повторы идут
|
||||||
|
непрерывно, пока стрелка зажата. Значит каждый повтор снимал реально зажатый
|
||||||
|
Shift, и к кадрам 102…106 (окно `check_grab`) движок видел Shift отпущенным.
|
||||||
|
Отсюда и «работает, если нажать Shift позже»: бит успевал постоять несколько
|
||||||
|
кадров до ближайшего повтора стрелки.
|
||||||
|
|
||||||
|
**Фикс.** Обратный вывод убран целиком — расширенная клавиша идёт обычным
|
||||||
|
путём и о состоянии Shift не судит. Прямой вывод оставлен (обёртка, если
|
||||||
|
клавиатура её всё-таки шлёт, подтверждает «Shift зажат» и стоит дёшево).
|
||||||
|
Состояние Shift теперь ведут его собственные make/break `12` / `F0 12` —
|
||||||
|
они приходят всегда. Заодно ушла ставшая ненужной переменная
|
||||||
|
`_kbdraw_fakesh`, и оба декодера стали короче (трамплину это на пользу: его
|
||||||
|
клавиатурный блок упирается в диапазон `jr`).
|
||||||
|
|
||||||
|
Файлы: `libc/irq/_irq_tramp.c`, `libc/kbd/kbd_raw_poll.c`, `libc/kbd/_kbdraw.h`,
|
||||||
|
`libc/kbd/_kbdraw_state.c`, `libc/kbd/kbd_raw_sync.c`.
|
||||||
|
|
||||||
|
**Чем платим.** Потерянный при Rx-overrun break Shift снять теперь нечем —
|
||||||
|
модификатор может залипнуть до перенажатия (это старый BUG-KBD-3). Размен
|
||||||
|
осознанный и решён в ту же сторону, что и раньше в `kbd_raw_sync`: **лучше
|
||||||
|
залипание, чем отвал** — залипший Shift игрок снимает нажатием Shift, а
|
||||||
|
сорванный посреди игры Shift в PoP стоит жизни. Вероятность overrun'а сильно
|
||||||
|
снижена дренажом FIFO опросом из главного цикла (`kbd_raw_poll`, KBD-1).
|
||||||
|
|
||||||
|
**Проверено в MAME после фикса** (карта `_kbdraw_down` при `roomtest`):
|
||||||
|
|
||||||
|
| действие | LSh (байт 2) | стрелки (байт 46) |
|
||||||
|
|---|---|---|
|
||||||
|
| зажать LShift | `04` | `00` |
|
||||||
|
| + зажать ↑ и → | `04` | `30` |
|
||||||
|
| держать 5 с (автоповтор идёт) | `04` | `30` |
|
||||||
|
| отпустить Shift, стрелки держать | `00` | `30` |
|
||||||
|
| отпустить стрелки | `00` | `00` |
|
||||||
|
|
||||||
|
`make size-check`: 70 программ, роста нет.
|
||||||
|
|
||||||
|
**Осталось наблюдением, не багом этой задачи.** В карте изредка остаётся
|
||||||
|
взведённым бит в PLAIN-половине для кода стрелки (напр. байт 14 бит 4 =
|
||||||
|
`0x74` «Right без E0»). Это потерянный префикс `E0` — след старого рассинхрона
|
||||||
|
FIFO. Игру не задевает (движок читает `KBD_RIGHT = EXT|0x74`, то есть
|
||||||
|
расширенную половину), но если всплывёт — искать здесь.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<a id="bug-kbd-4"></a>
|
<a id="bug-kbd-4"></a>
|
||||||
## BUG-KBD-4 (+ BUG-KBD-3). Shift: сначала залипал, потом стал отваливаться — **ЗАКРЫТ**
|
## BUG-KBD-4 (+ BUG-KBD-3). Shift: сначала залипал, потом стал отваливаться — **ЗАКРЫТ (с поправкой, см. BUG-KBD-5)**
|
||||||
|
|
||||||
|
> **Поправка 2026-08-05.** Вывод «клавиатура обёртывает КАЖДЫЙ расширенный
|
||||||
|
> код, пока зажат Shift» оказался неверным, и построенный на нём обратный
|
||||||
|
> вывод («расширенный make без обёртки ⇒ Shift отпущен») убран — он ломал
|
||||||
|
> Shift вместе со стрелками. Разбор — [BUG-KBD-5](#bug-kbd-5). Проверка
|
||||||
|
> «пять тапов стрелки подряд» ниже проходила не потому, что вывод был верен,
|
||||||
|
> а потому что между тапами бит восстанавливал автоповтор самого Shift.
|
||||||
|
> Актуальное поведение `kbd_raw_sync` — вариант 1 (модификаторы не сбрасываем).
|
||||||
|
|
||||||
**Симптом (вторая редакция).** Залипание ушло, но появилось обратное: при
|
**Симптом (вторая редакция).** Залипание ушло, но появилось обратное: при
|
||||||
зажатом Shift второй-третий-четвёртый тап стрелки отрабатывал уже не
|
зажатом Shift второй-третий-четвёртый тап стрелки отрабатывал уже не
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ BUG-GATE-PASS-1 — однократное наблюдение прохода
|
|||||||
|
|
||||||
| ID | что | тип | статус |
|
| ID | что | тип | статус |
|
||||||
|----|-----|-----|--------|
|
|----|-----|-----|--------|
|
||||||
| [Уровень 2](#уровень-2) | баги отрисовки с приёмки | — | **ждут списка от пользователя** |
|
| [BUG-GRAB-1](#bug-grab-1) | ур. 2 комн. 9: прыжок с места через 3 тайла — нет зацепа за кромку | Major | **причина найдена (клавиатура, BUG-KBD-5), фикс есть, ждёт игровой проверки** |
|
||||||
| [BUG-GATE-PASS-1](#bug-gate-pass-1) | проход сквозь закрывшуюся решётку (0,9) комнаты 5 | Major | **ждёт сценария воспроизведения** |
|
| [BUG-GATEMOD-1](#bug-gatemod-1) | ворота стартуют закрытыми, хотя в уровне открыты | Major | **фикс есть, ждёт проверки** |
|
||||||
|
| [Уровень 2](#уровень-2) | остальные баги с приёмки | — | принимаются по ходу |
|
||||||
|
| [BUG-GATE-PASS-1](#bug-gate-pass-1) | проход сквозь закрывшуюся решётку (0,9) комнаты 5 | Major | **перепроверить после [BUG-GATEMOD-1](#bug-gatemod-1)** — та же решётка стартовала не в том состоянии |
|
||||||
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт |
|
| [T-1](#t-1) | пики перерисовываются безусловно | оптимизация | открыт |
|
||||||
| [T-2](#t-2) | Кид перерисовывается в покое | оптимизация | открыт |
|
| [T-2](#t-2) | Кид перерисовывается в покое | оптимизация | открыт |
|
||||||
| [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия | открыт |
|
| [обход 24 комнат](#обход-всех-24-комнат-уровня-1) | таблица заполнена на 5 строк из 24 | ревизия | открыт |
|
||||||
@@ -54,10 +56,146 @@ BUG-GATE-PASS-1 — однократное наблюдение прохода
|
|||||||
вводит ровно три новых фоновых тайла — **большая колонна (низ 8 / верх 9)
|
вводит ровно три новых фоновых тайла — **большая колонна (низ 8 / верх 9)
|
||||||
и верх двери (12)**; если артефакт рядом с ними, это первый подозреваемый.
|
и верх двери (12)**; если артефакт рядом с ними, это первый подозреваемый.
|
||||||
|
|
||||||
*(записей пока нет)*
|
<a id="bug-grab-1"></a>
|
||||||
|
## BUG-GRAB-1. Прыжок с места через провал в 3 тайла: зацепа нет — **ПРИЧИНА НАЙДЕНА, ФИКС ЕСТЬ, ЖДЁТ ИГРОВОЙ ПРОВЕРКИ**
|
||||||
|
|
||||||
|
> **Итог 2026-08-05.** Физика тут ни при чём — виновата клавиатура.
|
||||||
|
> Зажатый Shift снимался автоповтором зажатой стрелки, поэтому к кадрам
|
||||||
|
> 102…106 (окно зацепа) движок видел Shift отпущенным. Полный разбор и
|
||||||
|
> фикс — [BUG-KBD-5](bug_closed.md#bug-kbd-5); поведение Shift в MAME
|
||||||
|
> проверено замером карты `_kbdraw_down`. Осталось подтвердить сам зацеп
|
||||||
|
> живой игрой; версии 2 и 3 ниже проверять только если он всё ещё не выйдет.
|
||||||
|
|
||||||
|
|
||||||
|
**Симптом.** Уровень 2, комната 9. Перепрыгнув на (1,1), Кид должен
|
||||||
|
вернуться обратно: разбегаться негде, поэтому он встаёт на самый край
|
||||||
|
плиты, прыгает с места и **цепляется руками за (1,5)**, после чего
|
||||||
|
подтягивается. У нас Кид с зажатым Shift всё равно срывается.
|
||||||
|
|
||||||
|
**Что уже точно известно (и не надо перепроверять).**
|
||||||
|
|
||||||
|
1. **Физика прыжка у нас совпадает с оригиналом кадр в кадр.** Сверено по
|
||||||
|
логу SDLPoP против трассы харнесса при одинаковом старте `x=95`:
|
||||||
|
|
||||||
|
```
|
||||||
|
кадр 16 18 22 23 24 25 102 103 104 105
|
||||||
|
SDLPoP 95 97 105 112 121 126 128 130 131 133
|
||||||
|
наш 95 97 105 112 121 126 128 130 131 133
|
||||||
|
```
|
||||||
|
Совпадает и по `y`, и по колонке/ряду, и по приземлению на 107–108.
|
||||||
|
|
||||||
|
2. **В оригинале зацеп срабатывает на кадре 106, а не 102..105.**
|
||||||
|
`check_grab` зовётся из ДВУХ мест: ветка «в воздухе» в `check_action`
|
||||||
|
(кадры 102..105) и `do_fall` (seg005) для `actions_4_in_freefall`.
|
||||||
|
Успешная попытка из лога:
|
||||||
|
|
||||||
|
```
|
||||||
|
GRAB try f=106 x=135 y=166 col=4 row=2 fall_y=18
|
||||||
|
GRAB probe x=127 col=4 through=0 front_above=3 modif=0
|
||||||
|
GRAB can_grab=1
|
||||||
|
GRAB OK dist=9
|
||||||
|
-> f=91 x=136 y=181 col=5 row=2 act=2 (повис)
|
||||||
|
```
|
||||||
|
Наш `do_fall` (`pop_map.c`) `check_grab()` из этой ветки тоже зовёт —
|
||||||
|
то есть структура на месте, расходится что-то внутри.
|
||||||
|
|
||||||
|
3. **По харнессу зацеп у нас РАБОТАЕТ**: окно стартовых `x = 91…95`, и
|
||||||
|
короткий шаг ставит Кида ровно туда (91 после первого нажатия, 95 после
|
||||||
|
второго). Зафиксировано тестом `t_grab`.
|
||||||
|
|
||||||
|
**Отсюда главный вопрос был: почему харнесс говорит «работает», а живая
|
||||||
|
машина — «нет».** Расхождение между ними и оказалось уликой; версии
|
||||||
|
выдвигались по убыванию правдоподобия, и сработала первая:
|
||||||
|
|
||||||
|
- **Shift не доезжает до движка — ПОДТВЕРЖДЕНО, это и была причина.**
|
||||||
|
Харнесс подменяет клавиатуру и потому этот путь не проверяет вовсе, а у
|
||||||
|
нас есть история проблем ровно с «Shift + стрелки» (KBD-1, BUG-KBD-3/4).
|
||||||
|
Замер в MAME: при зажатом Shift и зажатой стрелке бит `LSh` в
|
||||||
|
`_kbdraw_down` стоял в нуле. Разбор — [BUG-KBD-5](bug_closed.md#bug-kbd-5).
|
||||||
|
- **Сцена харнесса не равна комнате 9.** Там изолированная комната
|
||||||
|
(соседи — стена), а в игре слева комната 8; кромки шва участвуют в
|
||||||
|
`get_tile`. Проверять чтением `Kid.x` в момент прыжка: попал ли он в
|
||||||
|
окно 91…95 вообще.
|
||||||
|
- **Расхождение в `check_grab`.** Наш вариант зовёт `determine_col()`
|
||||||
|
там, где оригинал зовёт `load_fram_det_col()` (перезагрузка кадра +
|
||||||
|
колонка). Для Кида это обычно одно и то же (`cur_frame` в фазе физики
|
||||||
|
принадлежит ему), но проверить стоит.
|
||||||
|
|
||||||
|
**Инструменты готовы.** В SDLPoP включена отладка (пометка `DBG-GRAB`):
|
||||||
|
`JMP` — покадровая трасса прыжка/падения/виса, `GRAB try|probe|fail|OK` —
|
||||||
|
вход в `check_grab` и причина отказа. Снимается поиском по `DBG-GRAB`.
|
||||||
|
|
||||||
|
**Найдено попутно, отдельным наблюдением.** После касания площадки на
|
||||||
|
кадрах 107–108 (x=140) оба движка снова падают, но X расходится: SDLPoP
|
||||||
|
уводит Кида на 134 (колонка 4), мы — на 141 (колонка 5). Похоже на разную
|
||||||
|
отработку `in_wall()` у стены (2,7). На зацеп не влияет.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<a id="bug-gatemod-1"></a>
|
||||||
|
## BUG-GATEMOD-1. Ворота стартуют закрытыми, хотя в уровне открыты — **ФИКС ЕСТЬ, ЖДЁТ ПРОВЕРКИ**
|
||||||
|
|
||||||
|
**Симптом (пользователь, 2026-08-04).** Уровень 2, комната 13: решётка
|
||||||
|
между (2,5) и (2,6) обязана быть ОТКРЫТА в начале и захлопнуться, когда Кид
|
||||||
|
нажмёт кнопку (2,4) — после этого назад дороги нет. У нас она закрыта
|
||||||
|
сразу, кнопка бессмысленна, проход не работает.
|
||||||
|
|
||||||
|
**Корень.** Модификатор тайла в ФАЙЛЕ уровня и модификатор в РАНТАЙМЕ —
|
||||||
|
разные величины; оригинал переводит их при загрузке в `load_alter_mod`
|
||||||
|
(seg008:198E), которую зовёт `alter_mods_allrm` из `load_level`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
case tiles_4_gate: *modif = (*modif == 1) ? 188 : 0; break;
|
||||||
|
case tiles_11_loose:*modif = 0; break;
|
||||||
|
case tiles_10_potion:*modif <<= 3; break;
|
||||||
|
```
|
||||||
|
|
||||||
|
Наш `pop_trob_modif` портировал из неё **только зелье**. Для ворот
|
||||||
|
`bg = 1` — это «открыты» (Table 8 спецификации DAT), а в рантайме открытость
|
||||||
|
измеряется высотой подъёма 0..188; мы клали в рантайм-модификатор сырую
|
||||||
|
единицу, то есть «закрыты на 1/188».
|
||||||
|
|
||||||
|
**Фикс.** Ветки ворот и loose дописаны в ленивую инициализацию
|
||||||
|
`pop_trob_modif` (`pop_trob.c`). Ветка СТЕН не портируется намеренно: у нас
|
||||||
|
`pop_bg` считает связи кладки по типам соседей прямо при отрисовке
|
||||||
|
(`wall_modifier`), сохранённый модификатор стены не читается.
|
||||||
|
|
||||||
|
**Что это ещё задевает.** Решётка (0,9) комнаты 5 уровня 1 тоже имеет
|
||||||
|
`bg = 1`, то есть обязана стартовать открытой — Кид сваливается в комнату 1
|
||||||
|
именно через неё, и она захлопывается у него за спиной. Закрывает её
|
||||||
|
стартовый триггер `do_startpos` (seg003:167): для уровней с
|
||||||
|
`tbl_entry_pose == 1` оригинал ВИРТУАЛЬНО ЖМЁТ кнопку комнаты 5 (0,2) —
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Special event: press button + falling entry
|
||||||
|
get_tile(5, 2, 0); trigger_button(0, 0, -1); seqtbl_offset_char(seq_7_fall);
|
||||||
|
```
|
||||||
|
|
||||||
|
Замер в SDLPoP (лог по кадрам): `gate(5,0,9)` идёт `188 → 148 → 88 → 8 → 0`,
|
||||||
|
шаги 40/60/80 — это `gate_close_speeds`, то есть быстрое закрытие
|
||||||
|
(`trigger_gate` вернул тип 3). У нас этот триггер портирован, и закрытие
|
||||||
|
работает.
|
||||||
|
|
||||||
|
Полный список ворот с `bg = 1`: ур. 1 комн. 5 (0,9); ур. 2 комн. 13 (1,5).
|
||||||
|
Остальные ворота уровней 1–3 имеют `bg = 2` → 0, и для них ничего не
|
||||||
|
меняется (при модификаторе 2 и 0 и отрисовка, и `can_bump_into_gate` дают
|
||||||
|
одно и то же).
|
||||||
|
|
||||||
|
**Побочная находка: чит обхода комнат отматывал мир.** После фикса
|
||||||
|
пользователь увидел «ворота снова открылись», пройдя `+` в комнату 2 и `-`
|
||||||
|
обратно. Причина не в воротах: `ROOMNAV` звал `pop_trob_reset()` перед
|
||||||
|
`enter_room`, тот обнулял `room_seen[]`, и `pop_trob_modif()` перечитывал
|
||||||
|
модификаторы из уровня заново — то есть чит откатывал открытые/закрытые
|
||||||
|
ворота, выдвинутые пики и нажатые кнопки. Пока ворота с `bg=1` ошибочно
|
||||||
|
стартовали закрытыми, откат был не виден. `pop_trob_reset()` из навигации
|
||||||
|
убран: она обязана только телепортировать, исходное состояние даёт
|
||||||
|
перезапуск уровня. Замер, который это показал: `room_modif` комнаты 5
|
||||||
|
после `+`/`-` = `00 00 0B 00 09 00 08 01 00 BC` — последний байт 0xBC = 188,
|
||||||
|
файловое значение.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
<a id="ручная-перепроверка-2026-08-03"></a>
|
<a id="ручная-перепроверка-2026-08-03"></a>
|
||||||
# Ручная перепроверка фиксов (2026-08-03)
|
# Ручная перепроверка фиксов (2026-08-03)
|
||||||
|
|
||||||
|
|||||||
@@ -136,11 +136,15 @@ static uint8_t get_item_action(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* run_jump (seg005:0AA8). Выравнивание по кромке пола живёт в pop_map
|
||||||
|
* (там тайловые запросы) — pop_run_jump_align; здесь только диспетчерская
|
||||||
|
* часть. Отказ выравнивателя = прыжка в этом кадре НЕТ, и control_up
|
||||||
|
* гасить нельзя: Кид бежит дальше с зажатой «вверх» и попробует снова на
|
||||||
|
* следующем кадре. Ровно так игрок и «ловит» фазу перед провалом. */
|
||||||
static void run_jump(void)
|
static void run_jump(void)
|
||||||
{
|
{
|
||||||
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
|
|
||||||
* полировка K3; K2b просто запускает run-jump. */
|
|
||||||
if (Char.frame >= FRAME_7_RUN) {
|
if (Char.frame >= FRAME_7_RUN) {
|
||||||
|
if (!pop_run_jump_align()) return;
|
||||||
control_up = release_arrows();
|
control_up = release_arrows();
|
||||||
seqtbl_offset_char(SEQ_4_RUN_JUMP);
|
seqtbl_offset_char(SEQ_4_RUN_JUMP);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
|
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
|
||||||
#define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */
|
#define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */
|
||||||
#define SEQ_63_ACTIVE_AFTER_FALL 63 /* стойка С МЕЧОМ после приземления */
|
#define SEQ_63_ACTIVE_AFTER_FALL 63 /* стойка С МЕЧОМ после приземления */
|
||||||
|
#define SEQ_81_FIGHTFALL 81 /* fightfall: падение из боевой стойки */
|
||||||
|
|
||||||
#define TILE_FLOOR 1
|
#define TILE_FLOOR 1
|
||||||
#define FRAME_109_CROUCH 109
|
#define FRAME_109_CROUCH 109
|
||||||
@@ -522,11 +523,34 @@ static void land(void)
|
|||||||
static void start_fall(void)
|
static void start_fall(void)
|
||||||
{
|
{
|
||||||
uint8_t frame = Kid.frame, seq, tile;
|
uint8_t frame = Kid.frame, seq, tile;
|
||||||
|
/* seg006:1044 первым делом убирает меч в ножны: дальше падением рулит
|
||||||
|
* не боевой диспетчер, а обычный. Без этого Kid летит «с клинком», и
|
||||||
|
* control_with_sword разбирает кадры падения как боевые. */
|
||||||
|
Kid.sword = SWORD_0_SHEATHED;
|
||||||
inc_curr_row();
|
inc_curr_row();
|
||||||
|
/* start_chompers() — чомперов ещё нет (L3-CHOMP). */
|
||||||
/* seg006:1044 start_fall: frame 9 -> seq_7, 13 -> seq_19 (лишний dx(1)) */
|
/* seg006:1044 start_fall: frame 9 -> seq_7, 13 -> seq_19 (лишний dx(1)) */
|
||||||
if (frame == 13) seq = SEQ_19_FALL;
|
if (frame == 13) seq = SEQ_19_FALL;
|
||||||
else if (frame == 26) seq = SEQ_18_FALL_STANDJUMP;
|
else if (frame == 26) seq = SEQ_18_FALL_STANDJUMP;
|
||||||
else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP;
|
else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP;
|
||||||
|
else if (frame >= 81 && frame < 86) {
|
||||||
|
/* сорвался, приземляясь после прыжка вверх: сдвиг ВПЕРЁД на 5 */
|
||||||
|
seq = SEQ_19_FALL;
|
||||||
|
Kid.x = (uint8_t)char_dx_forward(5);
|
||||||
|
pop_load_fram_det_col();
|
||||||
|
}
|
||||||
|
else if (frame >= 150 && frame < 180) {
|
||||||
|
/* Кадры С МЕЧОМ (150..179) — отступил в провал. У оригинала это
|
||||||
|
* ОТДЕЛЬНАЯ последовательность seq_81 (fightfall): падение строго
|
||||||
|
* вниз (set_fall(0,15)), а не seq_7 с дрейфом fall_x=1 — из-за
|
||||||
|
* дрейфа наш Kid уезжал на ~тайл в сторону за время падения.
|
||||||
|
* Плюс сдвиг на 5 назад, если стоит у самой кромки лицом влево.
|
||||||
|
* Ветка стража (seq_82/83) не нужна: start_fall — только Kid.
|
||||||
|
* TODO: droppedout=1 (guard_follows_kid_down, guards.c:296). */
|
||||||
|
if (Kid.direction < 0 && distance_to_edge_weight() <= 7)
|
||||||
|
Kid.x = (uint8_t)char_dx_forward(-5);
|
||||||
|
seq = SEQ_81_FIGHTFALL;
|
||||||
|
}
|
||||||
else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */
|
else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */
|
||||||
kid_set_seq(seq);
|
kid_set_seq(seq);
|
||||||
pop_kid_play();
|
pop_kid_play();
|
||||||
@@ -760,6 +784,47 @@ uint8_t pop_jump_up_seq(void) __banked
|
|||||||
return jump_up_plain();
|
return jump_up_plain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* run_jump (seg005:0AA8), часть «выровнять по кромке» ------------------ *
|
||||||
|
* Оригинал НЕ отталкивается откуда попало: перед разбег-прыжком он смотрит
|
||||||
|
* на 1–2 тайла вперёд и, если там провал (или пика), подгоняет X так, чтобы
|
||||||
|
* толчок пришёлся ровно на кромку пола. Иначе прыжок стартует в случайной
|
||||||
|
* фазе бегового цикла — а запаса у seq_4 почти нет: суммарный dx кадров
|
||||||
|
* 34..44 равен 62 px при ширине тайла 14, то есть ровно 4 колонки и меньше
|
||||||
|
* половины тайла сверху. Поэтому без выравнивания провал в ТРИ пустых
|
||||||
|
* тайла (перелёт с колонки 5 на колонку 1) становится непроходимым —
|
||||||
|
* BUG-RJUMP-1, уровень 2, комнаты 1 и 9.
|
||||||
|
*
|
||||||
|
* Возврат: 1 — прыгать (Kid.x уже подогнан), 0 — прыжок ОТМЕНИТЬ. Отмена
|
||||||
|
* в оригинале НЕ гасит control_up: Кид бежит дальше с зажатой «вверх», и на
|
||||||
|
* следующем кадре попытка повторяется — так игрок ловит нужную фазу, просто
|
||||||
|
* удерживая клавишу. */
|
||||||
|
uint8_t pop_run_jump_align(void) __banked
|
||||||
|
{
|
||||||
|
int xpos = char_dx_forward(4);
|
||||||
|
int8_t col = get_tile_div_mod_m7(xpos);
|
||||||
|
uint8_t tf;
|
||||||
|
|
||||||
|
for (tf = 0; tf < 2; tf++) {
|
||||||
|
uint8_t t;
|
||||||
|
col = (int8_t)(col + dir_front[Kid.direction + 1]);
|
||||||
|
t = get_tile(col, Kid.curr_row);
|
||||||
|
if (t != TILE_SPIKE && tile_is_floor(t)) continue; /* тут пол — дальше */
|
||||||
|
|
||||||
|
{
|
||||||
|
int adj = distance_to_edge(xpos) + TILE_SIZEX * (int)tf - TILE_SIZEX;
|
||||||
|
/* Оригинал сравнивает БЕЗЗНАКОВО:
|
||||||
|
* if ((word)adj < (word)-8 || adj >= 2) { if (adj < 128) return; adj = -3; }
|
||||||
|
* что означает ровно «adj НЕ попал в [-8,-1] → прыжка нет».
|
||||||
|
* Ветка с adj = -3 недостижима: distance_to_edge ∈ [0,13] и
|
||||||
|
* tf ∈ {0,1} дают adj ∈ [-14,13], а туда нужно adj >= 128. */
|
||||||
|
if (adj < -8 || adj > -1) return 0;
|
||||||
|
Kid.x = (uint8_t)char_dx_forward((int8_t)(adj + 4));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
|
/* Стена впереди достаточно близко для стопа бега? Детект (без сдвига —
|
||||||
* позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */
|
* позицию держит check_bumped=in_wall). d<=2: передний край почти у грани. */
|
||||||
int pop_wall_ahead(void) __banked
|
int pop_wall_ahead(void) __banked
|
||||||
|
|||||||
@@ -154,6 +154,13 @@ uint8_t pop_edge_type(void) __banked;
|
|||||||
* control_running (чистый стоп у стены). */
|
* control_running (чистый стоп у стены). */
|
||||||
int pop_wall_ahead(void) __banked;
|
int pop_wall_ahead(void) __banked;
|
||||||
|
|
||||||
|
/* Разбег-прыжок: выравнивание по кромке пола (run_jump, seg005:0AA8).
|
||||||
|
* Смотрит на 1–2 тайла вперёд; если там провал/пика — подгоняет Kid.x под
|
||||||
|
* толчок ровно с кромки. 1 = прыгать, 0 = ОТМЕНИТЬ прыжок (Кид не в той
|
||||||
|
* фазе относительно кромки; control_up гасить НЕЛЬЗЯ — попытка повторится
|
||||||
|
* на следующем кадре). Без этого провал в три тайла непроходим. */
|
||||||
|
uint8_t pop_run_jump_align(void) __banked;
|
||||||
|
|
||||||
/* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО
|
/* Прыжок вверх в стойке (↑): seq чистого прыжка (seq_28/seq_14) ЛИБО
|
||||||
* прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x.
|
* прыжка-с-зацепом за уступ выше (seq_8/24/16) + выравнивание Kid.x.
|
||||||
* check_jump_up/grab_up (K4.4). control_up гасит caller. */
|
* check_jump_up/grab_up (K4.4). control_up гасит caller. */
|
||||||
|
|||||||
@@ -23,3 +23,23 @@ uint8_t pop_immortal;
|
|||||||
* clip_char из pop_map. См. pop_state.h. */
|
* clip_char из pop_map. См. pop_state.h. */
|
||||||
int pop_leveldoor_right;
|
int pop_leveldoor_right;
|
||||||
int pop_leveldoor_ybottom;
|
int pop_leveldoor_ybottom;
|
||||||
|
|
||||||
|
/* ---- Отладочная «пустышка» для брейкпоинтов из БАНКОВ ---------------- *
|
||||||
|
* Зачем. PC-брейкпоинт видит только логический адрес, а 0xC000+ — это
|
||||||
|
* окно, куда мапятся ВСЕ банки: точка на адресе банковой функции ловит
|
||||||
|
* заодно чужой код, случайно легший по тому же смещению (проверено:
|
||||||
|
* точка на start_fall из банка 3 срабатывала на каждом кадре — попадала
|
||||||
|
* в pop_bg из банка 2). Условные брейкпоинты этот отладчик MAME не
|
||||||
|
* поддерживает («error in assignment expression» на `==`).
|
||||||
|
*
|
||||||
|
* Приём (идея пользователя, 2026-08-04): позвать ЭТУ функцию ровно из
|
||||||
|
* того места банкового кода, которое отлаживаем, и поставить брейкпоинт
|
||||||
|
* на неё — она резидентна в W1, её адрес однозначен. По возврату из неё
|
||||||
|
* читаем что нужно. Условие «когда именно ловить» пишется обычным `if`
|
||||||
|
* в C — это гибче любых выражений отладчика.
|
||||||
|
*
|
||||||
|
* Счётчик нужен, чтобы вызов не выкинул оптимизатор; заодно видно, сколько
|
||||||
|
* раз точка прошла, если ловим не останавливаясь. */
|
||||||
|
uint8_t pop_dbg_hits;
|
||||||
|
|
||||||
|
void pop_dbg_trap(void) { pop_dbg_hits++; }
|
||||||
|
|||||||
@@ -29,4 +29,10 @@ extern uint8_t pop_immortal;
|
|||||||
extern int pop_leveldoor_right;
|
extern int pop_leveldoor_right;
|
||||||
extern int pop_leveldoor_ybottom;
|
extern int pop_leveldoor_ybottom;
|
||||||
|
|
||||||
|
/* Отладочная «пустышка» для брейкпоинтов из банков — см. pop_state.c.
|
||||||
|
* Звать из отлаживаемого места под нужным `if`, брейкпоинт ставить на
|
||||||
|
* _pop_dbg_trap (резидент W1, адрес однозначен). */
|
||||||
|
extern uint8_t pop_dbg_hits;
|
||||||
|
void pop_dbg_trap(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */
|
#define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */
|
||||||
#define TILE_OPENER 0x0F
|
#define TILE_OPENER 0x0F
|
||||||
#define TILE_POTION 0x0A
|
#define TILE_POTION 0x0A
|
||||||
|
#define TILE_LOOSE 0x0B /* проваливающийся пол (фаза тряски в modif) */
|
||||||
#define TILE_SWORD 0x16 /* меч-предмет на полу */
|
#define TILE_SWORD 0x16 /* меч-предмет на полу */
|
||||||
#define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */
|
#define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */
|
||||||
#define TILE_TORCH 0x13
|
#define TILE_TORCH 0x13
|
||||||
@@ -67,12 +68,31 @@ uint8_t *pop_trob_modif(uint8_t room)
|
|||||||
if (!room_seen[room - 1]) {
|
if (!room_seen[room - 1]) {
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
pop_level_room_bg(room, room_modif[room - 1]);
|
pop_level_room_bg(room, room_modif[room - 1]);
|
||||||
/* seg009 load_level: у ЗЕЛЬЯ модификатор сдвигается влево на 3 —
|
/* load_alter_mod (seg008:198E): модификатор В ФАЙЛЕ уровня и
|
||||||
* старшие биты = тип зелья, младшие 3 = фаза пузырька. */
|
* модификатор В РАНТАЙМЕ — разные величины, оригинал переводит их
|
||||||
|
* при загрузке. Порт того, что нас касается:
|
||||||
|
*
|
||||||
|
* ворота 1 = «открыты» (Table 8 спецификации DAT) -> 188, то есть
|
||||||
|
* рабочая высота подъёма; ЛЮБОЕ другое значение -> 0
|
||||||
|
* (закрыты). Без этого перевода ворота, которые в
|
||||||
|
* уровне открыты, стартуют закрытыми, и кнопка-closer
|
||||||
|
* рядом с ними теряет смысл (ур. 2, комн. 13).
|
||||||
|
* loose -> 0: фаза тряски всегда начинается с нуля.
|
||||||
|
* зелье -> <<3: старшие биты = тип, младшие 3 = фаза пузырька.
|
||||||
|
*
|
||||||
|
* Ветка стен (blue-line/связи кладки) НЕ портируется намеренно: у
|
||||||
|
* нас pop_bg считает связи по типам СОСЕДЕЙ прямо при отрисовке
|
||||||
|
* (wall_modifier), сохранённый модификатор стены не читается вовсе. */
|
||||||
pop_level_access_begin();
|
pop_level_access_begin();
|
||||||
for (i = 0; i < POP_ROOMTILES; i++)
|
for (i = 0; i < POP_ROOMTILES; i++) {
|
||||||
if ((pop_level_tile_raw(room, i) & 0x1F) == TILE_POTION)
|
uint8_t *m = &room_modif[room - 1][i];
|
||||||
room_modif[room - 1][i] = (uint8_t)(room_modif[room - 1][i] << 3);
|
switch (pop_level_tile_raw(room, i) & 0x1F) {
|
||||||
|
case TILE_GATE: *m = (uint8_t)((*m == 1) ? 188 : 0); break;
|
||||||
|
case TILE_LOOSE: *m = 0; break;
|
||||||
|
case TILE_POTION: *m = (uint8_t)(*m << 3); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
pop_level_access_end();
|
pop_level_access_end();
|
||||||
room_seen[room - 1] = 1;
|
room_seen[room - 1] = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user