Страж возвращался в комнату не туда: колонку несёт guards_x, а не тайл
Уровень 8 комната 24: после первого же выхода Кида страж телепортировался в колонку 0, а ряд 0 там пустой в колонках 1..3 — страж падал с ряда 0 на ряд 2 и разбивался. leave_guard (seg002:02F5) кладёт в guards_tile get_tilepos(0, row), то есть обнуляет колонку НАМЕРЕННО: позицию по горизонтали несёт guards_x, туда же она и пишется. enter_guard берёт x оттуда всегда. Мы же считали x из tile % 10 (то есть из нуля), а запомненную брали только у трупа. Заодно портирован pos_guards (seg003:0913): при загрузке уровня guards_x пересчитывается из колонки тайла, а файловое значение выбрасывается — в комнате 24 уровня 8 там 255. Проверено в MAME: три входа подряд дают одну и ту же позицию (x 156, ряд 0), страж стоит на полу. Host-тесты 5106 проверок без расхождений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3085,3 +3085,70 @@ else died_on_button(...); /* мёртв — кнопка С
|
|||||||
досчитать до 0 и снять trob. С фиксом сцена так не воспроизводится
|
досчитать до 0 и снять trob. С фиксом сцена так не воспроизводится
|
||||||
(решётка уходит в `FF`), проверить нечем; если всплывёт где-то ещё — это
|
(решётка уходит в `FF`), проверить нечем; если всплывёт где-то ещё — это
|
||||||
отдельный баг, к `died_on_button` отношения не имеющий.
|
отдельный баг, к `died_on_button` отношения не имеющий.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<a id="guard-respawn-col0"></a>
|
||||||
|
## GUARD-RESPAWN-COL0. Страж при возврате в комнату телепортировался в колонку 0 и падал
|
||||||
|
|
||||||
|
**Наблюдение (пользователь, 2026-08-18).** Уровень 8, комната 24. Первый
|
||||||
|
вход из левой комнаты — страж около `(0,7)`, стоит. Кид уходит вправо и
|
||||||
|
возвращается — страж уже «в `(0,-1)`». Ещё раз туда-обратно — страж на
|
||||||
|
`(0,0)`, идёт биться, падает на два ряда и разбивается. Второй сценарий:
|
||||||
|
вход из комнаты 23, страж на `(0,7)` неподвижен; Кид уходит влево и
|
||||||
|
возвращается — страж падает на `(2,1)` и разбивается.
|
||||||
|
|
||||||
|
Формулировка пользователя — «страж, если не может биться с Кидом, не должен
|
||||||
|
менять позиции; иначе он будет ждать Кида прямо на входе, и тот не успеет
|
||||||
|
достать меч» — совпадает с тем, что гарантирует оригинал.
|
||||||
|
|
||||||
|
**Как это устроено в оригинале.** Позиция стража живёт в ТРЁХ полях данных
|
||||||
|
уровня, и колонки среди них нет:
|
||||||
|
|
||||||
|
- `pos_guards` (seg003:0913), один раз сразу после `load_level`:
|
||||||
|
`guards_x[room] = x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX`.
|
||||||
|
То, что лежит в `guards_x` В ФАЙЛЕ, оригинал ВЫБРАСЫВАЕТ (в комнате 24
|
||||||
|
уровня 8 там 255);
|
||||||
|
- `leave_guard` (seg002:02F5) при уходе Кида:
|
||||||
|
`guards_tile = get_tilepos(0, row)` — колонка обнуляется НАМЕРЕННО, тайл
|
||||||
|
несёт только РЯД, — и `guards_x = Guard.x`, то есть настоящая позиция;
|
||||||
|
- `enter_guard` (seg002:0112) при возврате: `curr_row = tile / 10`,
|
||||||
|
`y = y_land[row + 1]`, **`x = guards_x`**, `curr_col = f(x)`.
|
||||||
|
|
||||||
|
Носитель колонки — `guards_x`, и только она.
|
||||||
|
|
||||||
|
**Что было у нас.** `pop_guard_leave` тайл обнуляла верно, а
|
||||||
|
`pop_guard_enter` считала X из `tile % 10` — из колонки, которой там уже нет.
|
||||||
|
Запомненную X брали ТОЛЬКО у трупа. Значит после ПЕРВОГО же выхода из
|
||||||
|
комнаты живой страж возвращался в колонку 0.
|
||||||
|
|
||||||
|
Численно: тайл стража комнаты 24 = 6 (ряд 0, колонка 6), ряд 0 =
|
||||||
|
`13 00 00 00 0F 01 01 01 01 01`, то есть **колонки 1..3 ПУСТЫЕ**, а в
|
||||||
|
колонке 0 факел. Телепорт давал `x = x_bump[5] + 14 = 72`, а 72 по таблице
|
||||||
|
деления — это колонка **1**: страж оказывался над дырой, падал с ряда 0 на
|
||||||
|
ряд 2 (щебень в `(2,1)`) — два ряда, смерть. Оба сценария пользователя
|
||||||
|
ложатся сюда ровно, включая «упал на 2,1».
|
||||||
|
|
||||||
|
**Фикс.** Два места, оба — приведение к оригиналу:
|
||||||
|
|
||||||
|
- `pop_gstate_init` — порт `pos_guards`: при загрузке уровня `GS_X`
|
||||||
|
считается из колонки тайла, а не читается из файла;
|
||||||
|
- `pop_guard_enter` — `Guard.x` берётся из запомненной ВСЕГДА, не только у
|
||||||
|
трупа; колонка, как и раньше, считается из X.
|
||||||
|
|
||||||
|
**Проверка в MAME** (уровень 8, комната 24, три входа через `enter_room`):
|
||||||
|
|
||||||
|
| | room | x | y | col | row | alive |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| первый вход | 24 | 156 | 55 | 7 | 0 | жив |
|
||||||
|
| возврат | 24 | 156 | 55 | 7 | 0 | жив |
|
||||||
|
| второй возврат | 24 | 156 | 55 | 7 | 0 | жив |
|
||||||
|
|
||||||
|
Позиция не меняется, страж стоит на полу. Host-тесты — 5106 проверок без
|
||||||
|
расхождений. Резидент без изменений, банк 8 −15 Б.
|
||||||
|
|
||||||
|
**Что осталось невыясненным.** Наблюдение «страж был в `(0,-1)`» отдельно
|
||||||
|
не воспроизведено: телепорт даёт колонку 0/1, а −1 требует `x < 65`.
|
||||||
|
Вероятнее всего это кадр уже НАЧАВШЕГОСЯ падения (при падении X сносит), но
|
||||||
|
доказательства нет. Если `(0,-1)` появится на исправленном билде — это
|
||||||
|
отдельный баг.
|
||||||
|
|||||||
@@ -111,13 +111,25 @@ void pop_guard_enter(uint8_t room)
|
|||||||
* записанный в его комнате. */
|
* записанный в его комнате. */
|
||||||
pop_guard_set_palette(pop_level_guard_type(pop_current_level) == 0
|
pop_guard_set_palette(pop_level_guard_type(pop_current_level) == 0
|
||||||
? color : 0);
|
? color : 0);
|
||||||
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
|
/* enter_guard (seg002:0112). Из ТАЙЛА берётся только РЯД — колонки в нём
|
||||||
* колонки тайла, а не берётся из файла. */
|
* нет: leave_guard кладёт туда get_tilepos(0, row), то есть обнуляет её
|
||||||
|
* намеренно. Носитель колонки — guards_x, её пишет тот же leave_guard, а
|
||||||
|
* при загрузке уровня заполняет pos_guards (см. pop_gstate_init).
|
||||||
|
*
|
||||||
|
* Считать X из `tile % 10`, как мы делали, значит после ПЕРВОГО же выхода
|
||||||
|
* из комнаты телепортировать живого стража в КОЛОНКУ 0 — потому что там
|
||||||
|
* с этого момента ноль. Уровень 8, комната 24: страж стоит в (0,6), ряд 0
|
||||||
|
* в колонках 1..3 ПУСТОЙ, и после возврата Кида страж оказывался над
|
||||||
|
* дырой (x = 72 — это колонка 1 по таблице деления), падал с ряда 0 на
|
||||||
|
* ряд 2 и разбивался. Наблюдения пользователя — «страж был на 0,0»,
|
||||||
|
* «страж падает на 2,1 и разбивается» — это ровно оно.
|
||||||
|
*
|
||||||
|
* Y всегда по ряду: enter_guard ставит стража НА ПОЛ (y_land[row+1]), а не
|
||||||
|
* туда, где он был по вертикали. Поэтому «дорисовать падение» при
|
||||||
|
* возврате в комнату оригинал не умеет — и не должен. */
|
||||||
Guard.room = room;
|
Guard.room = room;
|
||||||
Guard.curr_row = (int8_t)(tile / 10);
|
Guard.curr_row = (int8_t)(tile / 10);
|
||||||
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
|
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
|
||||||
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
|
||||||
Guard.curr_col = (int8_t)(tile % 10);
|
|
||||||
Guard.direction = dir;
|
Guard.direction = dir;
|
||||||
/* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он
|
/* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он
|
||||||
* встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке
|
* встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке
|
||||||
@@ -142,22 +154,21 @@ void pop_guard_enter(uint8_t room)
|
|||||||
* ровно там, где лёг (порт ветки guards_seq_hi != 0). */
|
* ровно там, где лёг (порт ветки guards_seq_hi != 0). */
|
||||||
{
|
{
|
||||||
uint16_t saved_seq = pop_guard_state_seq(room);
|
uint16_t saved_seq = pop_guard_state_seq(room);
|
||||||
/* У ЗАПОМНЕННОГО трупа X берём сохранённую: pos_guards (seg003:620)
|
/* X — ВСЕГДА из запомненной (enter_guard, seg002:179:
|
||||||
* пересчитывает её из колонки только при загрузке уровня, а дальше
|
* `Char.x = level.guards_x[room_minus_1]`), и живого стража это
|
||||||
* ею владеет leave_guard. Иначе тело при возврате прыгает в центр
|
* касается ровно так же, как трупа: страж, с которым Кид не стал
|
||||||
* тайла. */
|
* драться, обязан остаться СТОЯТЬ ТАМ ЖЕ. Иначе он не просто
|
||||||
if (saved_seq) Guard.x = pop_guard_state_x(room);
|
* «дёргается» — он может встретить Кида прямо на входе в комнату,
|
||||||
/* ...и КОЛОНКУ пересчитываем из X (enter_guard, seg002:180:
|
* не дав тому достать меч. */
|
||||||
* `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Брать её из
|
Guard.x = pop_guard_state_x(room);
|
||||||
* тайла нельзя: leave_guard сохраняет get_tilepos(0, row), то
|
/* КОЛОНКА — из X (enter_guard, seg002:180:
|
||||||
* есть колонку 0 всегда. У живого стража это незаметно (X там
|
* `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Из тайла её брать
|
||||||
* сам считается из колонки), а у ЗАПОМНЕННОГО ТРУПА X
|
* нельзя, там всегда 0 (см. выше). Колонка нужна не только
|
||||||
* настоящая, и curr_col оставался нулём при теле, лежащем в
|
* отрисовке: на неё смотрят коллизия и check_can_guard_see_kid, а у
|
||||||
* колонке 2. Тайл трупа шёл в обходе раньше тайла Кида, и Кид
|
* ЗАПОМНЕННОГО ТРУПА ещё и порядок обхода тайлов — иначе Кид
|
||||||
* рисовался ПОВЕРХ тела вместо того, чтобы пройти за ним
|
* рисовался поверх тела вместо того, чтобы пройти за ним
|
||||||
* (BUG-DRAWORDER-1). Колонка нужна не только отрисовке — на
|
* (BUG-DRAWORDER-1). */
|
||||||
* неё смотрят коллизия и check_can_guard_see_kid. */
|
Guard.curr_col = guard_col_from_x(Guard.x);
|
||||||
Guard.curr_col = guard_col_from_x(Guard.x);
|
|
||||||
pop_loadshad();
|
pop_loadshad();
|
||||||
if (saved_seq) Char.curr_seq = saved_seq;
|
if (saved_seq) Char.curr_seq = saved_seq;
|
||||||
else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
|
else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <sprite.h> /* gfx_w0_map / gfx_w0_unmap */
|
#include <sprite.h> /* gfx_w0_map / gfx_w0_unmap */
|
||||||
#include "pop_level.h"
|
#include "pop_level.h"
|
||||||
#include "_pop_level.h"
|
#include "_pop_level.h"
|
||||||
|
#include "pop_geom.h" /* pop_x_bump / FIRST_ONSCREEN_COLUMN — pos_guards */
|
||||||
|
|
||||||
/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с
|
/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с
|
||||||
* замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на
|
* замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на
|
||||||
@@ -247,6 +248,15 @@ void pop_gstate_init(void) __banked
|
|||||||
g[GS_DIR] = p[BP_GDIR + r];
|
g[GS_DIR] = p[BP_GDIR + r];
|
||||||
g[GS_X] = p[BP_GX + r];
|
g[GS_X] = p[BP_GX + r];
|
||||||
g[GS_SKILL] = p[BP_GSKILL + r];
|
g[GS_SKILL] = p[BP_GSKILL + r];
|
||||||
|
/* pos_guards (seg003:0913), сразу после load_level: X стража
|
||||||
|
* ПЕРЕСЧИТЫВАЕТСЯ из колонки тайла, а то, что лежит в файле, оригинал
|
||||||
|
* выбрасывает (в комнате 24 уровня 8 там, например, 255). Дальше X
|
||||||
|
* владеет leave_guard, и именно она — единственный носитель КОЛОНКИ:
|
||||||
|
* в guards_tile leave_guard кладёт get_tilepos(0, row), то есть
|
||||||
|
* колонку обнуляет намеренно. */
|
||||||
|
if (g[GS_TILE] < 30)
|
||||||
|
g[GS_X] = (uint8_t)(pop_x_bump[(g[GS_TILE] % 10) + FIRST_ONSCREEN_COLUMN]
|
||||||
|
+ TILE_SIZEX);
|
||||||
/* guards_seq_lo/hi в ФАЙЛЕ не используются — там 0xFF во всех
|
/* guards_seq_lo/hi в ФАЙЛЕ не используются — там 0xFF во всех
|
||||||
* комнатах (оригинал чистит их в reset_level_unused_fields).
|
* комнатах (оригинал чистит их в reset_level_unused_fields).
|
||||||
* Осмысленное значение туда кладёт только leave_guard, поэтому
|
* Осмысленное значение туда кладёт только leave_guard, поэтому
|
||||||
|
|||||||
Reference in New Issue
Block a user