PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат

Скелет (L3-SKEL, ассеты + механика):
- pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета
  poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3
  curr_guard_color = 0, оригинал палитру не подменяет);
- pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ
  УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом
  стража и был невидим;
- load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень —
  только в кадрах 150..189.  Пока ветка была одна (charid_2_guard), скелет
  получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался;
- check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в
  комнате 3 при падении (seg002:252), autocontrol_skeleton;
- leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня.

Цвета стражей (BUG-GUARD-COLOR-1, закрыт):
- все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по
  guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257).
  Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP
  меняется вместе со стражем.  Грабля: gfx_pal_load отдаёт указатель в BIOS,
  а тот читает только #4000-#BFFF — таблицу из банка копируем в стек.

Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт):
- pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19.
  Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж
  после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать
  обратно.  +48 байт W2.

Окклюзия соперника:
- pop_fore_over_char получил проход other_overlay_tile (порядок midtable,
  seg008:1B06) и расширение перебора объединённым прямоугольником
  «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх
  кладки и верхней грани пола;
- клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка
  (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP;
- ROOMNAV после смерти Кида делает честный pop_start_level: телепорт
  «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с
  вернувшейся кучей костей.

Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком
(101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) —
render_room анимированные тайлы пропускает, и в атласе не было ни одного.
Число EMM-страниц не изменилось.

Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок
(резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен
гард «код наехал на данные» (DATA_LOC).

Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с
разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех
Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3:
level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT).

В roomtest.c временно оставлен автостоп на падении соперника (отладка
падений скелета) — помечен ВРЕМЕННО.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 22:11:03 +03:00
parent 18d177407b
commit 6673279cef
55 changed files with 3340 additions and 1304 deletions
+38 -5
View File
@@ -43,7 +43,8 @@ OUT_DIR = os.path.join(HERE, "..", "poc", "res", "bg")
# Спрайты FORE-слоя (add_foretable в seg008 — «перед персонажем»): фронты
# столбов, ворот, дебриса. См. tile_table.fore_id + анализ pop_background_strategy.
FORE_ENV_IDS = {9, 12, 49, 88, 91, 95, 100}
FORE_ENV_IDS = {9, 12, 49, 88, 91, 95, 100} | {106, 107, 108, 109, 110} \
| {119, 120, 121, 122, 123} # фронт чомпера + его кровь
# Пол-оверлей подъёма (floor_left_overlay, draw_floor_overlay seg008): рисуется
# рантаймом поверх нижней части Kid при подтягивании на пол у кромки — НЕ
@@ -65,6 +66,23 @@ SPIKE_ANIM_ENV_IDS = {128, 129, 130, 131, 132, # left
134, 135, 136, 137, 138, # right
139, 140, 141, 142, 143} # fore
# Кадры ЧОМПЕРА (seg008:638 chomper_fram_bot/top/for + кровь). В уровнях
# чомпер стоит с modif = 0, а chomper_fram1[0] = 3 — значит render_room
# (он анимацию пропускает вовсе, см. render_room.py:611) не кладёт в атлас
# НИ ОДНОГО кадра, и чомпер не виден даже в покое. Поэтому весь набор —
# явно. Низ и верх идут в backtable (ПОД персонажем), фронт — в foretable
# (ПОВЕРХ), поэтому фронтовые id продублированы в FORE_ENV_IDS.
CHOMPER_BOT_IDS = {101, 102, 103, 104, 105} # chomper_fram_bot
CHOMPER_TOP_IDS = {111, 112, 113} # chomper_fram_top (фазы 2..4)
CHOMPER_FORE_IDS = {106, 107, 108, 109, 110} # chomper_fram_for
# Кровь на сомкнутых челюстях (modif & 0x80): chomper_num + 114 в backtable и
# + 119 в foretable, обе — MONO-блитом цветом 12 (blitters_4Ch_mono_12), то
# есть силуэтом, как пузырёк зелья. Появляется после смерти Кида в чомпере.
CHOMPER_BLOOD_IDS = {114, 115, 116, 117, 118}
CHOMPER_BLOOD_FORE_IDS = {119, 120, 121, 122, 123}
CHOMPER_BLOOD_COLOR = None # ставится ниже, когда известен VGA16_PAL_BASE
# Кадры ОТКРЫТИЯ ворот (portcullis, DOOR_FRAM_SLICE + gate pieces 50/51,
# seg008 draw_gate): 50-59 + 67. В уровнях ворота статически ЗАКРЫТЫ
# (низкий modif → используются только нижние срезы 50-53); срезы более
@@ -105,6 +123,7 @@ VGA16 = [(0,0,0),(0,0,42),(0,42,0),(0,42,42),(42,0,0),(42,0,42),(42,21,0),
(63,21,63),(63,63,21),(63,63,63)] # 6-битные каналы, как в .pal
POT_MASK_COLOR = VGA16_PAL_BASE + 0 # чёрный (blitters_40h_mono без цвета)
POT_BUBBLE_COLOR = VGA16_PAL_BASE + 12 # ярко-красный (color 12: «лечение»)
CHOMPER_BLOOD_COLOR = VGA16_PAL_BASE + 12 # тот же mono-цвет 12 (seg008:678)
# Основание факела (seg008:489 draw_tile_right: при левом соседе-факеле
# рисуется chtab_6 id 146). В статический render_room не попадает.
@@ -174,7 +193,7 @@ LEVELDOOR_ENV_IDS = {33, 34, 99, 144}
NO_TRANSP_ENV_IDS = {36, 43, 73, 74, 96, 149} | LEVELDOOR_ENV_IDS
def load_indexed(chtab, id, opaque=False):
def load_indexed(chtab, id, opaque=False, mono=None):
"""PNG (mode P, 16 цветов) -> (w, h, bytes 8bpp). Каскад VDUNGEON->VPALACE.
Пиксель 0 -> 0xFF (прозрач); i -> base+i (env 0x50 / wall 0x60).
@@ -195,7 +214,11 @@ def load_indexed(chtab, id, opaque=False):
if im.mode != "P":
die(f"{fname}: mode {im.mode}, ожидался P (16-цветный)")
src = im.tobytes()
if opaque:
if mono is not None:
# MONO-блит (blitters_4Ch_mono_12): спрайт красится ОДНИМ цветом из
# 16-цветной палитры экрана — пакуем силуэтом, как пузырёк зелья.
out = bytes((0xFF if v == 0 else mono) for v in src)
elif opaque:
out = bytes((pal_base + v) for v in src)
else:
out = bytes((0xFF if v == 0 else pal_base + v) for v in src)
@@ -363,6 +386,11 @@ def main():
| GATE_ANIM_ENV_IDS # + кадры открытия ворот
| TORCH_ENV_IDS # + основание факела
| STUCK_ENV_IDS # + нажатая кнопка-closer
| CHOMPER_BOT_IDS # + чомпер: низ
| CHOMPER_TOP_IDS # верх
| CHOMPER_FORE_IDS # фронт (поверх Kid)
| CHOMPER_BLOOD_IDS # кровь (mono)
| CHOMPER_BLOOD_FORE_IDS
| OPENER_NOFLOOR_ENV_IDS # + opener без пола слева
| LEVELDOOR_ENV_IDS) # + дверь уровня (створка/лестница)
wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL)
@@ -373,7 +401,9 @@ def main():
# ENV фон -> страницы по id>>SHIFT
pages = {}
for id in env_bg:
w, h, pix = load_indexed(R.CHTAB_ENV, id, id in NO_TRANSP_ENV_IDS)
w, h, pix = load_indexed(R.CHTAB_ENV, id, id in NO_TRANSP_ENV_IDS,
CHOMPER_BLOOD_COLOR if id in CHOMPER_BLOOD_IDS
else None)
pages.setdefault(id >> ENV_SHIFT, {})[id & ENV_MASK] = (w, h, pix)
max_pg = max(pages) if pages else -1
env_page_names = []
@@ -396,7 +426,10 @@ def main():
print(f" pop_wall.atl: {len(wall_ids)} спрайтов, каталог {cnt}, {sz} Б")
# FORE -> idx=id
fore_entries = {id: load_indexed(R.CHTAB_ENV, id) for id in env_fore}
fore_entries = {id: load_indexed(R.CHTAB_ENV, id, False,
CHOMPER_BLOOD_COLOR
if id in CHOMPER_BLOOD_FORE_IDS else None)
for id in env_fore}
cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_fore.atl"), fore_entries)
total_bytes += sz
print(f" pop_fore.atl: {len(env_fore)} спрайтов, каталог {cnt}, {sz} Б")