L6-SHADOW: тень роняет решётку + кромка ряда ниже
1. L6-SHADOW (seg002:0090 + seg002:1064). Тень встаёт при каждом входе в комнату 1 уровня 6 и делает ОДИН осторожный шаг (Shift+вперёд) в тот кадр, когда Кид прыгает к решётке (Kid.frame == 43, Kid.x < 128): встаёт на closer (1,1) и роняет решётку (1,2), за порожек которой Кид цепляется. do_init_shad переписан под таблицы init_shad_5/6 — первые 7 полей Char, как memcpy(&Char, source, 7) у оригинала. Проверено в MAME: «Кид прыгнул, зацепился, Тень сделал шаг, решётка упала». Тем самым закрыт остаток GUARD-PHYS — нажатие плиты НЕ-Кидом работает. 2. BUG-BELOWROW-WALL. При отсутствующей комнате снизу load_rowbelow (seg008:368) подставляет tiles_0_empty колонкам 1..9 и tiles_20_wall только левому краю; у нас стеной забивались все 11 позиций, и её topright лез жёлтыми треугольниками под пустые тайлы нижнего ряда (видно в шахте падения, комната 3). Комнаты 1 и 3 уровня 6 сверены с картой SDLPoP: расхождения только силуэты персонажей. tests-host 5/5, size-check чист. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,8 +55,6 @@
|
||||
|
||||
| # | Задача | Что | Блокирует |
|
||||
|---|--------|-----|-----------|
|
||||
| 1 | [L5-SHADOW](#l5-shadow) | **СЛЕДУЮЩАЯ**: тень уровня 5 (крадёт зелье в комнате 24) + движок автодвижений | прохождение ур. 5 |
|
||||
| 2 | [GUARD-PHYS](#guard-phys) | физика стража = физика Кида — ядро сделано, остаток: ветка ТЕНИ в `check_guard_fallout` и живая проверка кнопки под стражем | ур. 5+ (тень) |
|
||||
| — | [DRAW-COST](#draw-cost) | **кадр уложился в бюджет 2026-08-09**: 470 964 -> 425 600 тактов, период цикла 4 растровых кадра -> **3**. Дальнейшее — запас, не срочность | плавность на ВСЕХ уровнях |
|
||||
| — | [L1-SPEED](#l1-speed) | игра на ~39 % быстрее оригинала | ощущение от ВСЕХ уровней; берётся в любой момент |
|
||||
| — | [TUNE-1](#tune-1) | параметры движка → cfg-файл (сейчас `pop_tune.h`) | отладка таймингов и моды; берётся по мере надобности |
|
||||
@@ -89,167 +87,6 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
|
||||
|
||||
## P0 — делаем сейчас
|
||||
|
||||
### <a id="l5-shadow"></a>L5-SHADOW. Тень уровня 5: крадёт зелье — **СЛЕДУЮЩАЯ**
|
||||
|
||||
Единственная новая механика уровня 5 (тайлов новых нет вовсе, см. цель выше).
|
||||
Тень появляется в комнате 24, дожидается, пока откроется дверь, идёт к зелью,
|
||||
**выпивает его** и уходит за левый край. Боя нет.
|
||||
|
||||
**Как это в оригинале** (всё сверено по коду, `custom->*` — это дефолты 1.0):
|
||||
|
||||
| что | где | суть |
|
||||
|---|---|---|
|
||||
| появление | `check_shadow`, seg002:0064 | при СМЕНЕ КОМНАТЫ: если `current_level == 5` и `drawn_room == 24`, и тайл (кол 3, ряд 0) всё ещё `tiles_10_potion` — породить тень |
|
||||
| порождение | `do_init_shad`, seg002:0000 | `memcpy(&Char, init_shad_5, 7)` + `seqtbl_offset_char(2 /*stand*/)`, `charid = charid_1_shadow`, `demo_time = 0`, `guard_skill = 3`, `guardhp_* = 4`, `saveshad()` |
|
||||
| данные | `init_shad_5` | `{0x0F, 0x37, 0x37, 0, 0xFF, 0, 0}` = frame 15, x 55, y 55, direction 0, curr_col −1, curr_row 0, action 0 |
|
||||
| поведение | `autocontrol_shadow_level5`, seg002:1157 | в комнате 24: пока `demo_time == 0` — ждать, пока дверь (кол 1, ряд 0) не откроется (`modif >= 80`), затем `demo_index = 0`; дальше каждый кадр `do_auto_moves(shad_drink_move)`; при `Char.x < 15` — `clear_char()` |
|
||||
| движения | `do_auto_moves`, seg002:1089 | крошечный интерпретатор: `demo_time++`, по таблице `{time, move}` выбирается запись, `move` = 0 nothing / 1 forward / 2 backward / 3 up / 4 down / 5 up+forward / 6 shift / 7 move_7; −1 = ничего, −2 = конец |
|
||||
| таблица | `shad_drink_move` (data.h:866) | `{0x00,0} {0x01,1} {0x0E,0} {0x12,6} {0x1D,7} {0x2D,2} {0x31,1} {0xFF,−2}` — 8 записей по 2 байта |
|
||||
|
||||
**Что из этого у нас уже есть:**
|
||||
|
||||
- **механизм «спецсобытие порождает персонажа в слоте соперника»** —
|
||||
`pop_check_skel` (`guards.c`, зовётся из `roomtest.c` в тике); тень
|
||||
уровня 5 садится на тот же шов, только условие другое;
|
||||
- **тень как `charid_1_shadow`** — заведена под уровень 4
|
||||
([L4-MIRROR](TASKS_CLOSED.md#l4-mirror)): своя ветка ИИ
|
||||
(`autocontrol_shadow` + `autocontrol_shadow_level4`), выбор таблицы кадров
|
||||
Кида (`pop_frame_tbl_is_guard`), отрисовка спрайтами Кида;
|
||||
- **питьё зелья** — `SEQ_78_DRINK` и `get_item` в `pop_ctrl.c` (Кид уже
|
||||
умеет); тень «нажимает» те же кнопки через автодвижения;
|
||||
- **зелья как trob** — фаза пузырька, тип в старших битах (`pop_trob.c`).
|
||||
|
||||
**Что писать:**
|
||||
|
||||
1. `do_auto_moves` + таблица `shad_drink_move` + `demo_time`/`demo_index` —
|
||||
интерпретатор ~30 строк, кладётся рядом с `autocontrol_shadow` в
|
||||
`guards.c` (банк 1). «Движения» — это те же переменные управления, что
|
||||
заполняет `read_user_control` (`pop_ctrl.c`), так что `move_*` сводятся к
|
||||
присваиваниям.
|
||||
2. `do_init_shad(init_shad_5, seq stand)` — общий порождатель тени; пригодится
|
||||
и на уровнях 6 и 12 (`init_shad_6`, `init_shad_12` — те же 7 байт).
|
||||
3. Ветка `check_shadow` для уровня 5 — по образцу `pop_check_skel`, вызов из
|
||||
того же места тика.
|
||||
4. `autocontrol_shadow_level5`.
|
||||
5. **Ветка ТЕНИ в `check_guard_fallout`** (seg002:0241): тень падает, только
|
||||
если она в свободном полёте (`action == 4`), и тогда
|
||||
`loadshad(); clear_char(); saveshad()`. Сейчас в `pop_guard_fallout`
|
||||
(`pop_guard.c`) есть ветки стража и скелета, а тени нет — комментарий там
|
||||
обещает её «вместе с L3-SKEL», но она относится именно к тени.
|
||||
|
||||
**Чем подтверждать:** smoke уровня 5 — дойти до комнаты 24, увидеть, как
|
||||
тень выходит после открытия двери, выпивает зелье (тайл зелья исчезает) и
|
||||
уходит влево. Сверять последовательность движений с живым SDLPoP на том же
|
||||
месте — таблица `shad_drink_move` короткая, расхождение будет видно сразу.
|
||||
|
||||
**Оговорка по виду:** тень пока рисуется обычной копией спрайтов Кида, то
|
||||
есть выглядит вторым Кидом — это [отложенный](../docs/shadow_render.md)
|
||||
вопрос, к механике уровня 5 отношения не имеет.
|
||||
|
||||
### <a id="guard-phys"></a>GUARD-PHYS. Страж живёт по тем же правилам, что Кид — **ЯДРО СДЕЛАНО 2026-08-07**
|
||||
|
||||
> **Что уже работает** (решение пользователя: переносим физику на `Char`,
|
||||
> без предварительных замеров — иначе третий-четвёртый экземпляр той же
|
||||
> логики неизбежен).
|
||||
>
|
||||
> - **физика переведена на `Char`**: `pop_map.c` целиком работает с активным
|
||||
> персонажем, а кто в `Char` — решают окна `loadkid`/`savekid` и
|
||||
> `loadshad`/`saveshad`, как в оригинале (seg006:809). Два входа:
|
||||
> `pop_phys_tick` (порт хвоста play_kid_frame) и `pop_guard_phys_tick`
|
||||
> (порт play_guard_frame) — списки вызовов отличаются ровно тем, чем в
|
||||
> оригинале;
|
||||
> - **`take_hp` стал общим** (`pop_take_hp` в резиденте `pop_guard.c`): урон
|
||||
> идёт тому, кто в `Char`, по `charid`. Раньше у боёвки и у физики были
|
||||
> свои копии, причём у физики неверная — правила `hitp_curr` мимо дельты и
|
||||
> про не-Кидов не знала;
|
||||
> - **порт веток по charid в `land()`** (seg005:173): страж гибнет с двух
|
||||
> рядов, тень падает как с одного, у не-Кида приземление даёт боевую
|
||||
> стойку; `check_guard_bumped` (seg004:0522), `droppedout` +
|
||||
> `guard_follows_kid_down` (seg002:09F8), `check_guard_fallout`
|
||||
> (seg002:0241);
|
||||
> - **`pop_savekid_state` снова полное `Kid = Char`**, а
|
||||
> `pop_load_fram_det_col` пересчитывает колонку ЛЮБОМУ персонажу — обе
|
||||
> заплатки существовали только потому, что физика знала один `Kid`.
|
||||
>
|
||||
> **Проверено:** `tests-host` — трассы Кида не изменились (`[phys] ok: 1723`,
|
||||
> тот же эталон), новый набор `t_char` (32 проверки) покрывает ветки по
|
||||
> персонажам; в MAME проверено, что игра жива (респавн, бег, падение,
|
||||
> приземление). Цена: `_CODE` +80 Б, банк 3 (`pop_map`) 7973 → 8485
|
||||
> (51.8 %), банк 1 (`guards`) 2042 → 2156.
|
||||
>
|
||||
> **Проверено пользователем 2026-08-07:** страж СПРЫГИВАЕТ ЗА КИДОМ на ряд
|
||||
> ниже — связка «ИИ + физика» работает вживую, не только в тестах.
|
||||
>
|
||||
> **`follow_guard` портирован и проверен в MAME (2026-08-07):** уровень 1,
|
||||
> бой в комнате 3, Кид отступает влево — страж приходит следом (`Guard.room`
|
||||
> 3 → 2, X перенесён через шов), ровно как в SDLPoP. Условия отбора
|
||||
> покрыты тестами `t_char` (7 сценариев: пороги 91/165, «не бой», мёртвый,
|
||||
> вверх/вниз, занятая соседняя комната). **Сцена вскрыла отдельный баг —
|
||||
> [BUG-SWORD-GHOST-1](BUGS_CLOSED.md#bug-sword-ghost-1): при переходе в бою Кид
|
||||
> прячет меч и дальше дерётся пустой рукой.**
|
||||
>
|
||||
> **Осталось (потому и запись открыта) — ревизия 2026-08-11 по коду:**
|
||||
> 1. ~~`check_chomped_guard`~~ — **сделан** вместе с
|
||||
> [L3-CHOMP](TASKS_CLOSED.md#l3-chomp) (`pop_map.c`);
|
||||
> 2. ветки `check_guard_fallout`: **скелет сделан** (возрождается в комнате 3,
|
||||
> `pop_guard_fallout` в `pop_guard.c`), **ветки ТЕНИ нет** — падает только
|
||||
> в свободном полёте, `loadshad/clear_char/saveshad`; идёт в
|
||||
> [L5-SHADOW](#l5-shadow) п. 5 (комментарий в коде обещает её «вместе с
|
||||
> L3-SKEL» — устарел, это про тень);
|
||||
> 3. страж, нажимающий напольную кнопку, вживую не проверялся (код —
|
||||
> общий `check_press`).
|
||||
|
||||
**Почему это была ОДНА физика, а не «сделаем стражу свою».** В оригинале
|
||||
слот `Guard` — не «стражи», а все НЕ-Киды: тем же `play_guard_frame` ходят
|
||||
**страж** (charid 2), **скелет** (4, ур. 3), **тень** (1, ур. 4/5/6/12),
|
||||
**визирь-Джаффар** (ур. 13), **толстяк** (FAT, ур. 12) и **мышь** (0x18,
|
||||
ур. 8) — `tbl_guard_type` = {0,0,0,2,0,0,1,0,0,0,0,0,4,3,−1,−1}, а
|
||||
`autocontrol_opponent` (seg002:628) разводит их ТОЛЬКО по ИИ. То есть
|
||||
второй экземпляр логики пришлось бы делать не один раз, а пять.
|
||||
|
||||
**Гард по X: страж не уходит САМ — но его МОГУТ ПЕРЕНЕСТИ.** Поправка к
|
||||
формулировке, которая была здесь раньше («страж не покидает комнату ни в
|
||||
каком виде») — она неверна, контрпример дал пользователь: в SDLPoP страж из
|
||||
комнаты 3 оказывается в комнате 2 вслед за отступающим Кидом.
|
||||
|
||||
Разделять надо два разных механизма:
|
||||
|
||||
- **своим ходом — не может.** Физика персонажа слота Guard обёрнута
|
||||
`Char.room == drawn_room` и `Char.x >= 44 && Char.x < 211` (seg000:1252),
|
||||
и никакого `check_leave` в его списке вызовов нет. Провалившегося ниже
|
||||
комнаты убирает `check_guard_fallout` (seg002:0241) — вниз он не уходит.
|
||||
- **следом за Кидом — переносит движок.** `exit_room` (seg002:03C7)
|
||||
вызывается ПОСЛЕ того, как комнату сменил Кид, и решает судьбу стража:
|
||||
|
||||
```c
|
||||
if (Guard.alive < 0 && Guard.sword == sword_2_drawn) { // жив и В БОЮ
|
||||
if (guards_tile[kid_room−1] >= 30 || // в новой комнате
|
||||
guards_seq_hi[kid_room−1] != 0) { // своего живого нет
|
||||
if (ушёл ВЛЕВО) { if (Guard.x >= 91) leave = 1; } // страж далеко — остаётся
|
||||
else if (ВПРАВО) { if (Guard.x < 165) leave = 1; }
|
||||
else if (ВВЕРХ) { if (Guard.curr_row >= 0) leave = 1; } // всегда → не идёт
|
||||
else { if (Guard.curr_row < 3) leave = 1; } // вниз → не идёт
|
||||
} else leave = 1;
|
||||
} else leave = 1;
|
||||
leave ? leave_guard() : follow_guard();
|
||||
```
|
||||
|
||||
`follow_guard` (seg002:039E) стирает `guards_tile` в ОБЕИХ комнатах
|
||||
(0xFF — «стража здесь нет», чтобы он не раздвоился) и гонит стража через
|
||||
`goto_other_room` в окне `loadshad`/`saveshad`.
|
||||
|
||||
**Что это значит для нас.** У нас в `enter_room` (`roomtest.c:265`) стоит
|
||||
безусловный `pop_guard_leave()` — то есть всегда ветка `leave_guard`, и
|
||||
страж ВСЕГДА остаётся. Портировать надо сам `exit_room`-выбор: условия
|
||||
«жив + меч вынут + в целевой комнате нет своего стража + он у нужного
|
||||
края» и `follow_guard`. Пороги 91/165 — это «страж у того края, в который
|
||||
ушёл Кид»; вверх и вниз оригинал не пускает никогда.
|
||||
|
||||
**Чем подтверждать:** уровень 1, комната 3 — начать бой, отступить влево в
|
||||
комнату 2: страж обязан прийти следом и продолжить бой (как в SDLPoP на
|
||||
скриншотах пользователя). Обратная проверка: если в соседней комнате СВОЙ
|
||||
живой страж — переход не происходит.
|
||||
|
||||
### <a id="l3-color"></a>L3-COLOR. Палитра КЛАДКИ уровня 3 (в оригинале он зелёный)
|
||||
|
||||
**Наблюдение (пользователь, 2026-08-07, со сравнением карт VGA).** В
|
||||
|
||||
Reference in New Issue
Block a user