PoP roomtest: отрисовка стража в резидент W3 + fore-окклюзия + off-by-one спрайта

Разгрузка W1/W2 перед ИИ стражей (вариант 2 из двух обсуждённых).

1. pop_guard.c разделён по окнам: состояние/логика (Guard, HP, enter,
   kill, load_frame) остаются в W1/W2 — их обязан видеть банк guards.c;
   ОТРИСОВКА уехала в новый pop_gdraw.c, собираемый как --w3 (резидент).
   Правило границы: резидент = только то, что рисует и зовётся
   исключительно из главного цикла.  Кадр стража стал глобальным
   (pop_gframe): заполняет логика, читает резидент.

2. Страж не окклюдировался передними гранями тайлов — рисовался поверх
   столба.  В оригинале любой Char это запись midtable, а foretable
   рисуется после всех midtable (draw_tile_fore, seg008:690), т.е. столб
   перекрывает всех одинаково.  Футпринт персонажа выделен из
   pop_fore_over_kid в char_footprint(), поверх него добавлен
   pop_fore_over_char() — слой fore + полоса потолка, без оверлеев поз
   виса/полёта/подъёма (у стража их нет; появятся — портируем
   redraw_at_char2 общим кодом, а не догадками).

3. Упаковщик стража: тот же off-by-one, что уже ловили у Kid.
   load_chtab_from_file(id_chtab_5_guard, 750) даёт images[0] = res751,
   а рисование индексирует images[frame.image] — значит image=N это
   res(751+N), а не res(750+N).  Из-за сдвига frame_166_stand_inactive
   рисовался как res767 (выпад) вместо res768 (стойка).

Проверено в MAME: страж в комнатах 3 и 21 стоит в правильной позе;
окклюзия подтверждена патчем Guard.x в живой сессии — при заходе за
столб спрайт корректно срезается его передней гранью.

Память: _CODE 26 149 -> 25 703, куча W2 805 -> 1245 Б, резидент W3
11 656 -> 12 819 (свободно 3565 Б), банк 1 236/16384 Б.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 23:14:21 +03:00
parent 3dbad6120c
commit af5f0a4638
13 changed files with 217 additions and 112 deletions
+10 -1
View File
@@ -18,7 +18,7 @@
| 1. `kid_data.h` → EMM-страница + `load_frame`/`cur_frame` | **сделан** | `_CODE` −3 247 Б; попутно найден и обойдён баг кодогенерации SDCC (см. ниже) | | 1. `kid_data.h` → EMM-страница + `load_frame`/`cur_frame` | **сделан** | `_CODE` −3 247 Б; попутно найден и обойдён баг кодогенерации SDCC (см. ниже) |
| 2. `pop_geom.c` (дедуп геометрии + PRNG) | **сделан** | 41 Б `_CODE`, −11 Б W3; ценность — не байты, а bank-safe слой | | 2. `pop_geom.c` (дедуп геометрии + PRNG) | **сделан** | 41 Б `_CODE`, −11 Б W3; ценность — не байты, а bank-safe слой |
| 3. `pop_map` без вызовов графики | **сделан** (фазы 1a/1b) | через пометки перерисовки, см. ниже | | 3. `pop_map` без вызовов графики | **сделан** (фазы 1a/1b) | через пометки перерисовки, см. ниже |
| 4. Разгрузка/перебалансировка W3 | **сделан** | в резиденте остался ТОЛЬКО `pop_bg`; W3 14 376 → 11 632 (свободно 4 752 Б) | | 4. Разгрузка/перебалансировка W3 | **сделан** | резидент = `pop_bg` + `pop_gdraw` (отрисовка стража, 2026-07-29); W3 14 376 → 12 819 (свободно 3 565 Б) |
| 5. Данные (`room_modif`, `dl1/dl2`, `_kbdraw_down`) | не начат | потенциал ~1.5 КБ | | 5. Данные (`room_modif`, `dl1/dl2`, `_kbdraw_down`) | не начат | потенциал ~1.5 КБ |
| 6. Контракт банка стражей + пробник | **пробник сделан** | `tests/w3bankgfx` — модель подтверждена в MAME, см. ниже | | 6. Контракт банка стражей + пробник | **пробник сделан** | `tests/w3bankgfx` — модель подтверждена в MAME, см. ниже |
| 7. Дедуп семейства `draw_tile` | отложен по решению пользователя | «мороки много, выгода не так велика» | | 7. Дедуп семейства `draw_tile` | отложен по решению пользователя | «мороки много, выгода не так велика» |
@@ -27,6 +27,15 @@
W3-резидент 14 376 → 11 632 (свободно 2 008 → 4 752 Б). Сумма кода упала W3-резидент 14 376 → 11 632 (свободно 2 008 → 4 752 Б). Сумма кода упала
на ~3.2 КБ (данные Kid уехали в EMM), остальное — перераспределение. на ~3.2 КБ (данные Kid уехали в EMM), остальное — перераспределение.
**Замер 2026-07-29 (после стража).** Появление стража съело кучу до 805 Б;
разгрузка — вынос ОТРИСОВКИ стража в резидент (`pop_gdraw.c`, `--w3`), логика
и состояние остались в W1/W2, чтобы банк `guards.c` их видел (R2). Итог:
`_CODE` 26 149 → 25 703, куча **805 → 1 245 Б**, W3-резидент 11 656 → 12 819
(свободно 4 728 → 3 565 Б), банк 1 — 236 / 16 384 Б. Граница «что резидент»
теперь формулируется одним правилом: **резидент = только то, что рисует и
зовётся исключительно из главного цикла**; всё, что может понадобиться банку,
остаётся в W1/W2.
### Что сделано вместо §5.3 (вынос loose в W3) ### Что сделано вместо §5.3 (вынос loose в W3)
Вместо переноса кода между окнами выбран (по обсуждению с пользователем) Вместо переноса кода между окнами выбран (по обсуждению с пользователем)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -7
View File
@@ -6,12 +6,13 @@ EXAMPLE := roomtest
# small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе # small: W1+W2 как единое 32КБ-пространство, DATA сразу за CODE (иначе
# CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge). # CODE ~23КБ переполняет W1 и лезет в фиксированный DATA@0x8000 в huge).
MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи) MEMORY ?= huge # small-раскладка + банки кода в W3 (стражи)
# --w3 берёт ОДИН файл на флаг. В W3-резиденте держим ТОЛЬКО pop_bg — # --w3 берёт ОДИН файл на флаг (флагов можно несколько). В W3-резиденте
# отрисовку тайлов: она большая и её зовёт лишь главный цикл. Всё # держим ТОЛЬКО отрисовку, которую зовёт лишь главный цикл: pop_bg (тайлы)
# остальное живёт в W1/W2, чтобы оставаться достижимым из будущих # и pop_gdraw (спрайт стража). Всё остальное живёт в W1/W2, чтобы
# __banked-модулей (стражи): из банка резидентная страница W3 не видна — # оставаться достижимым из __banked-модулей (guards.c): из банка
# ни напрямую, ни транзитивно. См. docs/layout_plan_v2.md §2 (R2), §4. # резидентная страница W3 не видна — ни напрямую, ни транзитивно.
EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --bank 1=guards.c # См. docs/layout_plan_v2.md §2 (R2), §4.
EXTRA_FLAGS ?= --gfx 256 --w3 pop_bg.c --w3 pop_gdraw.c --bank 1=guards.c
EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c EXTRA_SRCS := pop_kid.c pop_ctrl.c pop_level.c pop_map.c pop_trob.c pop_redraw.c pop_geom.c pop_guard.c
BG_DIR := $(CURDIR)/../poc/res/bg BG_DIR := $(CURDIR)/../poc/res/bg
@@ -62,4 +63,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
cd $(TC) && python3 pop_extract_kid_data.py cd $(TC) && python3 pop_extract_kid_data.py
$(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA) $(EXAMPLE).exe: guards.c pop_guard.c pop_guard.h pop_gdraw.c pop_cheat.h pop_bg.c pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
+51 -3
View File
@@ -1325,13 +1325,20 @@ static void other_overlay_tile(int row, int col)
} }
} }
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, /* Футпринт персонажа в тайлах (порт set_char_collision, seg006:0723).
int8_t dir, uint8_t frame, uint8_t action) * Выделено из pop_fore_over_kid, потому что тем же счётом пользуется страж
* (pop_fore_over_char): в оригинале это общий код для любого Char.
* Результат — в файловых статиках, а не через 6 указателей: на Z80 такой
* возврат стоил бы дороже самой функции. */
static int8_t fp_cL, fp_cR, fp_cLraw, fp_rT, fp_rB, fp_rTraw;
static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir)
{ {
int wh = (int)((w + 1) >> 1); /* char_width_half */ int wh = (int)((w + 1) >> 1); /* char_width_half */
int xl = obj_x / 2 + 58; /* char_x_left (58-база) */ int xl = obj_x / 2 + 58; /* char_x_left (58-база) */
int xr; int xr;
int8_t cL, cR, cLraw, rT, rB, r, c, rTraw; int8_t cL, cR, cLraw, rT, rB, rTraw;
if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */ if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */
xr = xl + wh; xr = xl + wh;
cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */ cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */
@@ -1359,6 +1366,17 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
* (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим * (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим
* включение ряда rB-1. */ * включение ряда rB-1. */
if (rB > 0 && rT >= rB) rT = rB - 1; if (rB > 0 && rT >= rB) rT = rB - 1;
fp_cL = cL; fp_cR = cR; fp_cLraw = cLraw;
fp_rT = rT; fp_rB = rB; fp_rTraw = rTraw;
}
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir, uint8_t frame, uint8_t action)
{
int8_t cL, cR, cLraw, rT, rB, rTraw, r, c;
char_footprint(obj_x, obj_y, w, h, dir);
cL = fp_cL; cR = fp_cR; cLraw = fp_cLraw;
rT = fp_rT; rB = fp_rB; rTraw = fp_rTraw;
/* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт /* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт
* Kid попадает в midtable — база порядка окклюзии в other_overlay_tile. * Kid попадает в midtable — база порядка окклюзии в other_overlay_tile.
* run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/ * run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/
@@ -1427,3 +1445,33 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
} }
gfx_set_bank(GFX_BANK_NORMAL); gfx_set_bank(GFX_BANK_NORMAL);
} }
/* Передние грани тайлов поверх ПРОИЗВОЛЬНОГО персонажа (страж, дальше —
* тень/скелет). В оригинале любой Char — запись midtable, а foretable
* рисуется после всех midtable (draw_tile_fore, seg008:690), поэтому
* колонна и кромка пола перекрывают всех одинаково.
*
* Отличие от pop_fore_over_kid: только слой fore (+ полоса потолка).
* Оверлеи тела пола (draw_other_overlay) завязаны на позы Kid — вис,
* полёт, подъём, — которых у стража нет: он ходит и дерётся на полу
* (seq_77..seq_84). Появятся падающие/висящие стражи — тогда и общий
* порт redraw_at_char2, а не догадки сейчас. */
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir)
{
int8_t r, c;
char_footprint(obj_x, obj_y, w, h, dir);
gfx_set_bank(GFX_BANK_SPRITE);
for (r = fp_rT; r <= fp_rB; r++)
for (c = fp_cL; c <= fp_cR; c++)
fore_tile(r, c);
if (fp_rTraw < 0) {
g_clip_top = POP_YOFF;
for (c = fp_cL; c <= fp_cR; c++) {
ov_mark(COL_XH[c] * 8, 0, 64, CEIL_BAND_H);
ceil_over_kid_tile(c);
}
g_clip_top = 0;
}
gfx_set_bank(GFX_BANK_NORMAL);
}
+6
View File
@@ -58,6 +58,12 @@ void pop_room_set_above(const uint8_t *above_row2_fg, const uint8_t *above_row2_
void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h, void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir, uint8_t frame, uint8_t action); int8_t dir, uint8_t frame, uint8_t action);
/* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка,
* без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата
* (до ×8/7), как у kid_fp_obj_x(). */
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
int8_t dir);
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ /* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за * (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
* прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице * прямоугольник Kid, поэтому kid_heal его не убирает — на второй странице
+102
View File
@@ -0,0 +1,102 @@
/*
* pop_gdraw.c — ОТРИСОВКА стража. Резидент окна W3 (--w3 pop_gdraw.c).
*
* Почему отдельным файлом, а не внутри pop_guard.c: --w3 забирает МОДУЛЬ
* целиком (код+rodata в 0xC000), а состояние стража и его логика обязаны
* остаться в W1/W2 — из банка (guards.c) резидент W3 недостижим ни прямо,
* ни транзитивно (docs/layout_plan_v2.md §2 R2). Значит граница проходит
* ровно между «что рисует» (зовёт только главный цикл → резидент) и «что
* знает» (зовёт и банк → W1/W2).
*
* Писучие статики резидента (--dataseg не трогаем) остаются в обычном
* _DATA, то есть в W2 — сюда переезжает КОД, не данные.
*/
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_guard.h"
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */
#include "kid_data.h"
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */
#define GUARD_PAGES 5
static atlas_t gp[GUARD_PAGES];
static uint8_t g_ok;
/* Прямоугольник последней отрисовки ПО СТРАНИЦЕ дабл-буфера (как у Kid:
* heal обязан стирать спрайт именно той страницы, в которую рисуем). */
static int gx_l[2], gy_l[2];
static uint16_t gw_l[2], gh_l[2];
static uint8_t gvalid[2];
int pop_guard_load(void)
{
static const char *const names[GUARD_PAGES] = {
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
"GUARD\\g3.atl", "GUARD\\g4.atl"
};
uint8_t i;
for (i = 0; i < GUARD_PAGES; i++)
if (atlas_load(&gp[i], names[i]) != 0) return -1;
g_ok = 1;
return 0;
}
void pop_guard_free(void)
{
uint8_t i;
if (!g_ok) return;
for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]);
g_ok = 0;
}
void pop_guard_heal(void)
{
uint8_t p = gfx_get_draw_page() & 1;
if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
gvalid[p] = 0;
}
void pop_guard_draw(void)
{
int obj_x, obj_y, top, fwd, fp_x;
uint8_t page, idx, p, flip;
const uint8_t *img;
uint16_t w, h;
if (!g_ok || Guard.charid == 0 || pop_gframe.image == 255) return;
/* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */
fwd = (int)Guard.x + ((Guard.direction < 0) ? -pop_gframe.dx : pop_gframe.dx);
obj_x = (fwd << 1) - 116;
obj_y = (int)Guard.y + pop_gframe.dy;
if ((int8_t)(pop_gframe.flags ^ Guard.direction) >= 0) obj_x++;
fp_x = obj_x; /* ЛОГИЧЕСКАЯ X (до ×8/7) — футпринт */
obj_x = obj_x * 8 / 7; /* calc_screen_x_coord */
page = (uint8_t)(pop_gframe.image >> 3);
idx = (uint8_t)(pop_gframe.image & 7);
if (page >= GUARD_PAGES) return;
img = (const uint8_t *)atlas_image(&gp[page], idx);
gfx_w0_map(gp[page].page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
if (w && h) {
int bx = obj_x;
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
if (flip) bx -= (int)w;
top = obj_y - (int)h + 1;
p = gfx_get_draw_page() & 1;
gfx_set_bank(GFX_BANK_SPRITE);
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, 0, 0);
gfx_set_bank(GFX_BANK_NORMAL);
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = h; gvalid[p] = 1;
}
gfx_w0_unmap();
/* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись
* midtable, а foretable рисуется после всех midtable-записей, поэтому
* колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid
* (seg008:690 draw_tile_fore). Без этого страж виден сквозь передний
* столб. */
if (w && h)
pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction);
}
+17 -91
View File
@@ -1,21 +1,25 @@
/* /*
* pop_guard.c — состояние стража (W1/W2). См. pop_guard.h. * pop_guard.c — СОСТОЯНИЕ и ЛОГИКА стража (W1/W2). См. pop_guard.h.
* Логика — в банке (guards.c), отрисовка — из главного цикла. *
* Здесь только то, что должно оставаться достижимым из банка (guards.c):
* поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы.
* ОТРИСОВКА — в pop_gdraw.c, резиденте W3 (её зовёт лишь главный цикл).
*/ */
#include <graphics.h> #include <stdint.h>
#include <gfx.h>
#include <sprite.h>
#include "pop_guard.h" #include "pop_guard.h"
#include "pop_geom.h" #include "pop_geom.h"
#include "pop_level.h" #include "pop_level.h"
#include "pop_kid.h" /* play_seq/kid_set_seq пока общие (seqtbl один) */ #include "pop_kid.h" /* pop_kid_data_frame — таблицы в EMM-странице */
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
#include "kid_data.h" #include "kid_data.h"
pop_char_t Guard; pop_char_t Guard;
uint8_t guardhp_curr, guardhp_max; uint8_t guardhp_curr, guardhp_max;
int8_t guardhp_delta; int8_t guardhp_delta;
/* Кадр стража (порт cur_frame для Char). Глобальный, а не статик: его
* заполняет логика (W1/W2), а читает отрисовка из резидента W3. */
kframe pop_gframe;
uint8_t pop_guard_present(void) uint8_t pop_guard_present(void)
{ {
return (uint8_t)(Guard.charid != 0 && Guard.alive < 0 && guardhp_curr != 0); return (uint8_t)(Guard.charid != 0 && Guard.alive < 0 && guardhp_curr != 0);
@@ -45,51 +49,16 @@ void pop_guard_kill(void)
Guard.alive = 0; Guard.alive = 0;
} }
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */ /* load_frame для стража (seg006:0293 ветка charid_2_guard): таблица у
#define GUARD_PAGES 5 * стражей СВОЯ (frame_tbl_guard), индекс = frame + add_frame 149, где
static atlas_t gp[GUARD_PAGES];
static uint8_t g_ok;
/* Прямоугольник последней отрисовки ПО СТРАНИЦЕ дабл-буфера (как у Kid:
* heal обязан стирать спрайт именно той страницы, в которую рисуем). */
static int gx_l[2], gy_l[2];
static uint16_t gw_l[2], gh_l[2];
static uint8_t gvalid[2];
/* Кадр стража (порт cur_frame для Char): таблица у стражей СВОЯ
* (frame_tbl_guard), и индексируется как frame + add_frame 149, где
* add_frame = 70 для кадров 102..106 (seg006:535). */ * add_frame = 70 для кадров 102..106 (seg006:535). */
static kframe gframe; void pop_guard_load_frame(void)
int pop_guard_load(void)
{
static const char *const names[GUARD_PAGES] = {
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
"GUARD\\g3.atl", "GUARD\\g4.atl"
};
uint8_t i;
for (i = 0; i < GUARD_PAGES; i++)
if (atlas_load(&gp[i], names[i]) != 0) return -1;
g_ok = 1;
return 0;
}
void pop_guard_free(void)
{
uint8_t i;
if (!g_ok) return;
for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]);
g_ok = 0;
}
/* load_frame для стража (seg006:0293 ветка charid_2_guard). */
static void guard_load_frame(void)
{ {
int16_t idx = (int16_t)Guard.frame; int16_t idx = (int16_t)Guard.frame;
if (idx >= 102 && idx < 107) idx += 70; /* add_frame */ if (idx >= 102 && idx < 107) idx += 70; /* add_frame */
idx -= 149; idx -= 149;
if (idx < 0 || idx >= KID_NGFRAMES) { gframe.image = 255; return; } if (idx < 0 || idx >= KID_NGFRAMES) { pop_gframe.image = 255; return; }
pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&gframe); pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&pop_gframe);
} }
void pop_guard_enter(uint8_t room) void pop_guard_enter(uint8_t room)
@@ -118,48 +87,5 @@ void pop_guard_enter(uint8_t room)
Guard.frame = 166; Guard.frame = 166;
guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */ guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */
guardhp_delta = 0; guardhp_delta = 0;
guard_load_frame(); pop_guard_load_frame();
}
void pop_guard_heal(void)
{
uint8_t p = gfx_get_draw_page() & 1;
if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
gvalid[p] = 0;
}
void pop_guard_draw(void)
{
int obj_x, obj_y, top, fwd;
uint8_t page, idx, p, flip;
const uint8_t *img;
uint16_t w, h;
if (!g_ok || Guard.charid == 0 || gframe.image == 255) return;
/* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */
fwd = (int)Guard.x + ((Guard.direction < 0) ? -gframe.dx : gframe.dx);
obj_x = (fwd << 1) - 116;
obj_y = (int)Guard.y + gframe.dy;
if ((int8_t)(gframe.flags ^ Guard.direction) >= 0) obj_x++;
obj_x = obj_x * 8 / 7; /* calc_screen_x_coord */
page = (uint8_t)(gframe.image >> 3);
idx = (uint8_t)(gframe.image & 7);
if (page >= GUARD_PAGES) return;
img = (const uint8_t *)atlas_image(&gp[page], idx);
gfx_w0_map(gp[page].page);
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
if (w && h) {
int bx = obj_x;
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
if (flip) bx -= (int)w;
top = obj_y - (int)h + 1;
p = gfx_get_draw_page() & 1;
gfx_set_bank(GFX_BANK_SPRITE);
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, 0, 0);
gfx_set_bank(GFX_BANK_NORMAL);
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = h; gvalid[p] = 1;
}
gfx_w0_unmap();
} }
+11 -6
View File
@@ -7,9 +7,9 @@
* линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк — * линкуются В СТРАНИЦУ БАНКА и снаружи не читаются, поэтому банк —
* только код; * только код;
* - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c; * - ЛОГИКА (ИИ, боёвка) — в guards.c, собираемом как --bank 1=guards.c;
* - ОТРИСОВКА стража — из W1/W2 (рядом с kid_draw): из банка примитивы * - ОТРИСОВКА стража — в pop_gdraw.c, РЕЗИДЕНТЕ W3 (--w3): её зовёт
* libbgi звать можно, но резидент W3 (pop_bg) — нельзя, поэтому слой * только главный цикл, а из банка резидент недостижим. Так код
* фона/окклюзии за стража делает главный цикл. * отрисовки не занимает дефицитные W1/W2 (куча).
* *
* Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006 * Поля char_type — как в оригинале (types.h:302), чтобы порт seg005/seg006
* ложился один в один. * ложился один в один.
@@ -18,6 +18,7 @@
#define POP_GUARD_H #define POP_GUARD_H
#include <stdint.h> #include <stdint.h>
#include "kid_data.h" /* kframe */
/* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */ /* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */
#define CHARID_0_KID 0 #define CHARID_0_KID 0
@@ -45,6 +46,11 @@ extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты
extern uint8_t guardhp_curr, guardhp_max; extern uint8_t guardhp_curr, guardhp_max;
extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */ extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */
/* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2,
* читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */
extern kframe pop_gframe;
void pop_guard_load_frame(void);
/* Есть ли живой страж в отрисованной комнате (для главного цикла). */ /* Есть ли живой страж в отрисованной комнате (для главного цикла). */
uint8_t pop_guard_present(void); uint8_t pop_guard_present(void);
@@ -58,9 +64,8 @@ void pop_guard_free(void);
void pop_guard_enter(uint8_t room); void pop_guard_enter(uint8_t room);
/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE, /* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE,
* как kid_draw) и стереть прошлый (heal). Звать из главного цикла рядом * как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c
* с kid_draw: рисовать из БАНКА нельзя только резидентом W3, а тут мы в * (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */
* W1/W2. */
void pop_guard_heal(void); void pop_guard_heal(void);
void pop_guard_draw(void); void pop_guard_draw(void);
+12 -4
View File
@@ -28,12 +28,20 @@ PAL_BASE = 0x90 # слоты палитры Sprinter под страж
COLOR = 2 # level.guards_color обоих стражей уровня 1 COLOR = 2 # level.guards_color обоих стражей уровня 1
SHIFT = 3 # g[id>>3], idx id&7 (спрайты крупные) SHIFT = 3 # g[id>>3], idx id&7 (спрайты крупные)
MASK = (1 << SHIFT) - 1 MASK = (1 << SHIFT) - 1
MAX_ID = 34 # res751..res784 MAX_ID = 33 # атлас-индексы 0..33 = res751..res784
def load_guard(id): def load_guard(id):
"""id -> res(750+id).png -> (w, h, 8bpp COLUMN-MAJOR); 0 -> 0xFF (прозр.).""" """Атлас-индекс id (== frame.image) -> res(751+id).png -> (w, h, 8bpp
path = os.path.join(GUARD_DIR, f"res{750 + id}.png") COLUMN-MAJOR); 0 -> 0xFF (прозрачность).
Off-by-one как у Kid (см. pop_pack_kid.load_kid): load_chtab_from_file
зовётся с ресурсом 750, а images[i-1] = res(750+i), то есть images[0] =
res751. Рисование берёт get_image(chtab, obj_id+1 1) = images[image],
значит frame.image=N -> res(751+N). С res(750+N) страж стоял в позе
предыдущего кадра (замечено на frame_166_stand_inactive: image 17
рисовался как res767 — выпад — вместо res768). """
path = os.path.join(GUARD_DIR, f"res{751 + id}.png")
if not os.path.exists(path): if not os.path.exists(path):
return None return None
im = Image.open(path) im = Image.open(path)
@@ -55,7 +63,7 @@ def main():
os.makedirs(OUT_DIR, exist_ok=True) os.makedirs(OUT_DIR, exist_ok=True)
pages = {} pages = {}
n = 0 n = 0
for id in range(1, MAX_ID + 1): for id in range(0, MAX_ID + 1):
g = load_guard(id) g = load_guard(id)
if g is None: if g is None:
continue continue