# roomtest — проверка порта статического фона PoP (Шаг 3): реальная
# композиция слоёв (pop_bg.c) для комнаты 1.  --memory huge --gfx 256.

PROJ_ROOT   := $(abspath $(CURDIR)/../../..)
EXAMPLE     := roomtest
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
MEMORY      ?= huge        # small-раскладка + банки кода в W3 (стражи)
# Резидента --w3 БОЛЬШЕ НЕТ: отрисовка уехала в БАНК 2 (pop_bg + pop_cdraw
# в одной странице — вызовы между ними прямые, если функция не помечена
# __banked).  Так снято главное ограничение резидента: из банка его было
# не достать ни прямо, ни транзитивно, а банк→банк работает (трамплин
# сохраняет страницу окна на СТЕКЕ, проверено вложенной цепочкой
# банк1→банк2→банк1 в MAME 2026-07-30).  Теперь в банки можно уводить и
# логику, которая рисует.  См. docs/layout_plan_v2.md §2, memory
# sdcc_banked_call_rules.
# Банк 5 = pop_ctrl.c (MEM-BANK5).  CODE и DATA делят одно 32-КБ
# пространство W1+W2, поэтому килобайт кода, уехавший в банк, — это
# килобайт, доступный данным.  Кандидат выбран не по размеру, а по частоте
# вызова: диспетчер управления дёргается один раз за кадр на персонажа и
# горячих банк→банк переходов не создаёт (в отличие от pop_level, чей
# pop_level_tile зовётся из банка 2 на КАЖДЫЙ тайл).  Шина control_*
# переехала в pop_state.c — см. там.
# --max-allocs-per-node: во сколько вариантов размещения регистров SDCC
# упирается на узел.  У sprinter-cc дефолт 100000 (агрессивно, как fast-сборки
# библиотек) — на roomtest это МИНУТЫ на банк, потому что модули крупные.
# Дефолт SDCC — 3000, и для разработки его достаточно: разница в размере
# единицы процента, а сборка кратно быстрее.  Замер (2026-08-05) — в шапке
# TASKS_CLOSED.md, запись BUILD-FAST.
#
#   make                 — быстрая сборка (ALLOCS=3000)
#   make ALLOCS=100000   — как раньше: минимальный код, для замеров размера
#                          и для «релизного» образа
#
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
ALLOCS      ?= 3000
# Единственный список банковых исходников: из него строятся И ключи
# sprinter-cc, И зависимости roomtest.exe.  Раньше эти списки жили отдельно,
# поэтому добавленные pop_shadow.c/pop_sfx_cold.c компилировались, но make не
# замечал их последующие изменения и оставлял устаревший exe.
BANK1_SRCS := guards.c
BANK2_SRCS := pop_bg.c
BANK3_SRCS := pop_map.c
BANK4_SRCS := pop_cdraw.c pop_kdraw.c
BANK5_SRCS := pop_ctrl.c pop_tile_cold.c
BANK6_SRCS := pop_trob.c
BANK7_SRCS := pop_room.c pop_redraw.c
BANK8_SRCS := roomtest_cold.c pop_level_cold.c pop_kboot.c pop_guard_cold.c \
              pop_shadow.c pop_sfx_cold.c pop_qsave.c pop_qsave_coldio.c \
              pop_level_qsave.c
BANKED_SRCS := $(BANK1_SRCS) $(BANK2_SRCS) $(BANK3_SRCS) $(BANK4_SRCS) \
               $(BANK5_SRCS) $(BANK6_SRCS) $(BANK7_SRCS) $(BANK8_SRCS)
BANK_FLAGS := $(foreach f,$(BANK1_SRCS),--bank 1=$(f)) \
              $(foreach f,$(BANK2_SRCS),--bank 2=$(f)) \
              $(foreach f,$(BANK3_SRCS),--bank 3=$(f)) \
              $(foreach f,$(BANK4_SRCS),--bank 4=$(f)) \
              $(foreach f,$(BANK5_SRCS),--bank 5=$(f)) \
              $(foreach f,$(BANK6_SRCS),--bank 6=$(f)) \
              $(foreach f,$(BANK7_SRCS),--bank 7=$(f)) \
              $(foreach f,$(BANK8_SRCS),--bank 8=$(f))
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) \
               $(BANK_FLAGS) $(PROF_FLAGS)
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
# MAME = время фазы в растровых строках.
PROF        ?= 1
PROF_FLAGS  := -DPROF_BORDER=$(PROF)
# Стартовый уровень (отладка): make LEVEL=9 — начать сразу с девятого.
# Дефолт держим на отлаживаемом сейчас уровне; для «настоящей» игры — LEVEL=1.
# Константа объявлена в pop_tune.h (её видят обе половины главного цикла).
LEVEL       ?= 11
PROF_FLAGS  += -DFIRST_LEVEL=$(LEVEL)
# Стартовая КОМНАТА и позиция Кида в ней (отладка): make ROOM=15 POS=2 —
# начать прямо в целевой комнате оптимизации, минуя проход уровня.  POS —
# тайл 0..29, то есть row*10+col.  Дефолт — сцена 11/15 (Кид в 0,2), по
# которой сняты замеры в docs/perf_l11_room15.md.  make ROOM= (пусто)
# возвращает штатный старт из данных уровня.
ROOM        ?= 15
POS         ?= 2
ifneq ($(strip $(ROOM)),)
PROF_FLAGS  += -DDBG_START_ROOM=$(ROOM) -DDBG_START_POS=$(POS)
endif
EXTRA_SRCS  := pop_vflip.c pop_pace.c pop_sfx.c pop_state.c pop_draw.c pop_tile.c pop_kid.c pop_level.c pop_geom.c pop_guard.c pop_qsave_io.c

BG_DIR      := $(CURDIR)/../poc/res/bg
KID_DIR     := $(CURDIR)/../poc/res/kid
LVL_DIR     := $(CURDIR)/../SDLPoP/data/LEVELS
# ВСЕ уровни игры (res2001..res2015, по 2305 Б = 34 КБ на всех): переход
# уровень→уровень и чит Shift+L требуют, чтобы файл следующего лежал на
# диске.  res2000 — демо-уровень, в скоуп не входит.
LVL_NUMS    := 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
LVL_DATA    := $(foreach n,$(LVL_NUMS),$(LVL_DIR)/res20$(n).bin)
# ДВА тайлсета: pop_* — подземелье (уровни 1-3, 7-9, 12, 13, 15),
# pal_* — дворец (4-6, 10, 11, 14).  Раскладка id у них общая, различаются
# файлы и палитра тайлсета (*tile.pal, 32 записи).  chtab_1 (pop_pot.atl:
# пламя, меч-предмет, зелья) от набора не зависит — один на оба.
BG_SETS     := pop pal
BG_PAGES    := 0 1 2 3 4 5 6 7 8 9
BG_DATA     := $(foreach s,$(BG_SETS),\
                 $(foreach n,$(BG_PAGES),$(BG_DIR)/$(s)_env$(n).atl) \
                 $(BG_DIR)/$(s)_wall.atl $(BG_DIR)/$(s)_fore.atl \
                 $(BG_DIR)/$(s)_tile.pal) \
               $(BG_DIR)/pop_pot.atl
# ВСЕ 28 страниц Kid — иначе кадры торможения/разворота/приседа (image>23)
# не рисуются (отрисовка пропускает незагруженную страницу).  Палитра —
# ЕДИНАЯ игровая (env+wall+kid).
KID_PAGES   := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl)
# Зеркальный ПО ВЕРТИКАЛИ набор (зелье инверсии, уровень 9): те же спрайты с
# развёрнутыми колонками.  Готовит упаковщик, а не рантайм — разворот
# страницы стоит 0.59 с против 0.045 с на её чтение с диска (замер MAME
# 2026-08-12, протокол в pop_vflip.c).
KID_VPAGES  := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n)_v.atl)
KID_DATA    := $(KID_PAGES) $(KID_VPAGES) $(KID_DIR)/kid.pal \
               $(KID_DIR)/sword.atl $(KID_DIR)/sword_v.atl
# Таблицы анимации (кадры+seqtbl) — отдельный бинарь в EMM-странице, его
# делает pop_extract_kid_data.py (не упаковщик спрайтов) — своё правило ниже.
KID_BIN     := $(KID_DIR)/kid_data.bin
GUARD_DIR   := $(CURDIR)/../poc/res/guard
GUARD_DATA  := $(GUARD_DIR)/g0.atl $(GUARD_DIR)/g1.atl $(GUARD_DIR)/g2.atl \
               $(GUARD_DIR)/g3.atl $(GUARD_DIR)/g4.atl \
               $(GUARD_DIR)/g0_v.atl $(GUARD_DIR)/g1_v.atl $(GUARD_DIR)/g2_v.atl \
               $(GUARD_DIR)/g3_v.atl $(GUARD_DIR)/g4_v.atl
# Скелет — ОТДЕЛЬНЫЙ набор спрайтов (SKEL.DAT в оригинале): его грузит
# уровень 3 (tbl_guard_type == 2), кадров меньше (28 против 34).
SKEL_DIR    := $(CURDIR)/../poc/res/skel
# Зеркального набора у скелета НЕТ: зелье инверсии — уровень 9, там обычный
# страж (tbl_guard_type[9] = 0).  См. pop_pack_guard.py.
SKEL_DATA   := $(SKEL_DIR)/g0.atl $(SKEL_DIR)/g1.atl $(SKEL_DIR)/g2.atl \
               $(SKEL_DIR)/g3.atl
# Джафар — тоже отдельный набор (VIZIER.DAT): его грузит уровень 13
# (tbl_guard_type == 3).  Кадров столько же, сколько у стража (34), но
# палитра своя, из файла набора: curr_guard_color для не-стражей = 0.
# Зеркального набора нет по той же причине, что у скелета.
VIZIER_DIR  := $(CURDIR)/../poc/res/vizier
VIZIER_DATA := $(VIZIER_DIR)/g0.atl $(VIZIER_DIR)/g1.atl $(VIZIER_DIR)/g2.atl \
               $(VIZIER_DIR)/g3.atl $(VIZIER_DIR)/g4.atl

# ТЕНЬ — свой набор, запечённый упаковщиком (наложение спрайта на себя со
# сдвигом; наш блит так не умеет, см. ../docs/shadow_atlas_plan.md).  Две
# половины в одном наборе: sk* — кадры вне боя (спрайты Кида), sf* — кадры
# 150..189 (SHADOW.DAT, это тоже графика Кида, а не стража).
SHADOW_DIR  := $(CURDIR)/../poc/res/shadow
SHADOW_DATA := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(SHADOW_DIR)/sk$(n).atl) \
               $(foreach n,0 1 2 3,$(SHADOW_DIR)/sf$(n).atl)

# ЗВУКОВЫЕ ЭФФЕКТЫ — оригинальная оцифровка, приведённая к частоте CBL
# (10 937,5 Гц, 8 бит моно).  8 страниц по 16 КБ; см. ../docs/sound_plan.md.
SND_DIR     := $(CURDIR)/../poc/res/sound
SND_DATA    := $(foreach n,0 1 2 3 4 5 6 7,$(SND_DIR)/s$(n).bin)

EXTRA_DATA  := $(BG_DATA) $(KID_DATA) $(KID_BIN) $(GUARD_DATA) $(SKEL_DATA) \
               $(VIZIER_DATA) $(SHADOW_DATA) $(SND_DATA) $(LVL_DATA)
# Образ HDD для MAME: ресурсы раскладываются ПО КАТАЛОГАМ (8.3, как в DSS),
# чтобы корень диска не зарастал десятками .atl.
#   BG\     — фон (env/wall/fore/pot)
#   KID\    — персонаж (kid0..27, палитра, меч, таблицы анимации)
#   GUARD\  — стражи (появятся здесь же)
#   LEVELS\ — уровни
HDD_PACK_ARGS := $(EXAMPLE).exe \
                 $(foreach f,$(BG_DATA),BG:$(f)) \
                 $(foreach f,$(KID_DATA) $(KID_BIN),KID:$(f)) \
                 $(foreach f,$(GUARD_DATA),GUARD:$(f)) \
                 $(foreach f,$(SKEL_DATA),SKEL:$(f)) \
                 $(foreach f,$(VIZIER_DATA),VIZIER:$(f)) \
                 $(foreach f,$(SHADOW_DATA),SHADOW:$(f)) \
                 $(foreach f,$(SND_DATA),SND:$(f)) \
                 $(foreach f,$(LVL_DATA),LEVELS:$(f))

include $(PROJ_ROOT)/app.mk

TC := $(PROJ_ROOT)/applications/PoP/toolchain

# Упаковщики создают сразу ГРУППУ файлов.  Обычное multi-output правило
# заставляло GNU make при `-B` запускать один и тот же упаковщик отдельно для
# каждого .atl/.bin.  Stamp-файл представляет один запуск генератора; все
# созданные им файлы зависят от этого stamp без собственных рецептов.
RESOURCE_STAMP_DIR := .resource-stamps
BG_STAMP      := $(RESOURCE_STAMP_DIR)/bg
GUARD_STAMP   := $(RESOURCE_STAMP_DIR)/guard
SKEL_STAMP    := $(RESOURCE_STAMP_DIR)/skel
VIZIER_STAMP  := $(RESOURCE_STAMP_DIR)/vizier
SHADOW_STAMP  := $(RESOURCE_STAMP_DIR)/shadow
SOUND_STAMP   := $(RESOURCE_STAMP_DIR)/sound
KID_STAMP     := $(RESOURCE_STAMP_DIR)/kid
KID_BIN_STAMP := $(RESOURCE_STAMP_DIR)/kid-bin

$(RESOURCE_STAMP_DIR):
	mkdir -p $@

$(BG_STAMP): $(TC)/pop_pack_bg.py $(TC)/render_room.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_pack_bg.py dungeon && python3 pop_pack_bg.py palace
	touch $@
$(BG_DATA): $(BG_STAMP)
	@test -f $@ || { $(MAKE) -B $(BG_STAMP); test -f $@; }

$(GUARD_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_pack_guard.py
	touch $@
$(GUARD_DATA): $(GUARD_STAMP)
	@test -f $@ || { $(MAKE) -B $(GUARD_STAMP); test -f $@; }

$(SKEL_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_pack_guard.py SKEL
	touch $@
$(SKEL_DATA): $(SKEL_STAMP)
	@test -f $@ || { $(MAKE) -B $(SKEL_STAMP); test -f $@; }

$(VIZIER_STAMP): $(TC)/pop_pack_guard.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_pack_guard.py VIZIER
	touch $@
$(VIZIER_DATA): $(VIZIER_STAMP)
	@test -f $@ || { $(MAKE) -B $(VIZIER_STAMP); test -f $@; }

$(SHADOW_STAMP): $(TC)/pop_pack_shadow.py $(TC)/pop_pack_bg.py $(TC)/pop_pack_kid.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_pack_shadow.py
	touch $@
$(SHADOW_DATA) pop_shadow_atlas.h: $(SHADOW_STAMP)
	@test -f $@ || { $(MAKE) -B $(SHADOW_STAMP); test -f $@; }

$(SOUND_STAMP): $(TC)/pop_pack_sound.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_pack_sound.py
	touch $@
$(SND_DATA) pop_sound_tbl.h: $(SOUND_STAMP)
	@test -f $@ || { $(MAKE) -B $(SOUND_STAMP); test -f $@; }

$(KID_STAMP): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_pack_kid.py
	touch $@
$(KID_DATA): $(KID_STAMP)
	@test -f $@ || { $(MAKE) -B $(KID_STAMP); test -f $@; }

$(KID_BIN_STAMP): $(TC)/pop_extract_kid_data.py | $(RESOURCE_STAMP_DIR)
	cd $(TC) && python3 pop_extract_kid_data.py
	touch $@
kid_data.h $(KID_BIN): $(KID_BIN_STAMP)
	@test -f $@ || { $(MAKE) -B $(KID_BIN_STAMP); test -f $@; }

# Явные цели позволяют собирать ресурсы без `make -B`, который по смыслу
# принудительно пересобирает также libc, libbgi и само приложение.
resources-bg: $(BG_DATA)
resources-kid: $(KID_DATA) $(KID_BIN)
resources-actors: $(GUARD_DATA) $(SKEL_DATA) $(VIZIER_DATA) $(SHADOW_DATA)
resources-sound: $(SND_DATA)
resources-levels: $(LVL_DATA)
resources: resources-bg resources-kid resources-actors resources-sound resources-levels
resources-rebuild:
	$(MAKE) -B resources

# Принудительная перелинковка только roomtest, без глобального `-B` и без
# принудительной пересборки библиотек/ресурсов.
relink: resources
	$(MAKE) -W Makefile $(EXAMPLE).exe

# HDD_PACK_ARGS содержит все эти файлы; make обязан подготовить их до запуска
# упаковщика и при чистой сборке, а не полагаться на старое содержимое дерева.
hdd: resources

# sprinter-cc собирает все модули одним вызовом и depfile пока не выдаёт.
# Банковые .c берём из того же списка, что формирует --bank; любой локальный
# заголовок также обязан перелинковать приложение.  wildcard намеренно
# широкий: цена лишней полной сборки ниже риска получить stale-бинарник.
LOCAL_HEADERS := $(wildcard *.h)
$(EXAMPLE).exe: Makefile $(PROJ_ROOT)/app.mk $(BANKED_SRCS) $(LOCAL_HEADERS) \
                $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)

clean-resources:
	rm -rf $(RESOURCE_STAMP_DIR)

.PHONY: resources resources-rebuild resources-bg resources-kid resources-actors relink \
        resources-sound resources-levels clean-resources
