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:
@@ -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**
|
||||
|
||||
Самый неприятный класс: не косметика, а неверные РЕШЕНИЯ движка, причём
|
||||
@@ -523,13 +668,15 @@ MAME по кромкам.
|
||||
|
||||
## НЕ БАГИ (кривая картинка, но совпадает с оригиналом — НЕ чинить)
|
||||
|
||||
- **Отход с ВЫНУТЫМ МЕЧОМ роняет Кида в яму «раньше, чем кажется»** (комната 4
|
||||
уровня 2, кромка ряда 1 у дыры от упавшей loose-плиты). Сверено с SDLPoP
|
||||
v1.24 пользователем (2026-08-04): оригинал падает **с той же позиции, по той
|
||||
же траектории и с тем же видом кадра падения** (голова/руки поверх кромки
|
||||
пола). Наш кадр и кадр SDLPoP совпадают один в один.
|
||||
- **Кадр падения с мечом: голова/руки поверх кромки пола — САМ РЕНДЕР верен.**
|
||||
Сверено с SDLPoP v1.24 (2026-08-04): там кадр падения выглядит один в один.
|
||||
⚠ Речь ТОЛЬКО о виде кадра. Расхождение по X, которое было отмечено здесь
|
||||
как открытое, разобрано и закрыто: [BUG-FALL-SWORD-1](#bug-fall-sword-1) —
|
||||
дело не в моменте срыва (он верен), а в том, что мы играли `seq_7` вместо
|
||||
`seq_81` и получали горизонтальный дрейф `set_fall(1,15)` за всё падение.
|
||||
|
||||
Механика, чтобы не разбирать заново. У стоек с мечом ОГРОМНАЯ точка веса:
|
||||
Механика точки веса, чтобы не разбирать заново. У стоек с мечом она
|
||||
ОГРОМНАЯ:
|
||||
|
||||
```
|
||||
кадр 158/170/171 (stand_with_sword): dx=0 weight_x=13
|
||||
@@ -745,8 +892,105 @@ foretable — в той же странице по смещению `0x1000` (с
|
||||
|
||||
# Вторая волна прогона 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>
|
||||
## 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 второй-третий-четвёртый тап стрелки отрабатывал уже не
|
||||
|
||||
Reference in New Issue
Block a user