b5d2a81ee3
Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём. Разбор каждого — bug_closed.md.
Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
(POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
роняло Кида сквозь пол.
Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
(redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.
По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
нарисовался поверх передних столбов (kid_fore_clip_restore);
2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
настоящей X. Теперь колонка выводится из X, как в оригинале;
3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
«упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
габарита, поэтому бегущий Кид уходит за объекты справа. Считается
для обоих персонажей — enter_guard ставит action=1 и стражу.
Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража. make size-check — роста нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
238 lines
12 KiB
C
238 lines
12 KiB
C
/*
|
||
* 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_kid.h" /* pop_sword_draw — клинок общий с Кидом */
|
||
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */
|
||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида */
|
||
#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];
|
||
|
||
/* Клинок — ОТДЕЛЬНЫМ прямоугольником, не объединением со стражем: объединение
|
||
* почти вдвое больше суммы двух (клинок уходит вперёд-вверх), а heal стоит
|
||
* ровно по площади. Объединение считаем только для окна fore-клипа. */
|
||
static int sx_l[2], sy_l[2];
|
||
static uint16_t sw_l[2], sh_l[2];
|
||
static uint8_t svalid[2];
|
||
|
||
/* Ширина спрайта последнего кадра стража — нужна set_char_collision
|
||
* (char_width_half) при вычислении его ТАЙЛА ОБЪЕКТА. Не по страницам:
|
||
* это свойство кадра, а не буфера. Отставание на кадр допустимо — так же
|
||
* работает kid_fp_width() у Кида. */
|
||
static uint16_t g_last_w;
|
||
|
||
uint16_t pop_guard_fp_width(void) __banked { return g_last_w; }
|
||
|
||
int pop_guard_load(void) __banked
|
||
{
|
||
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) __banked
|
||
{
|
||
uint8_t i;
|
||
if (!g_ok) return;
|
||
for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]);
|
||
g_ok = 0;
|
||
}
|
||
|
||
/* Стирание прошлого кадра стража и его клинка. pop_heal_fast — по той же
|
||
* причине, что в kid_heal: стираем ровно то, что рисовали, а рисовали в
|
||
* большинстве кадров noclip-ядром. Два прямоугольника, а не объединение
|
||
* (см. комментарий к sx_l выше). */
|
||
void pop_guard_heal(void) __banked
|
||
{
|
||
uint8_t p = gfx_get_draw_page() & 1;
|
||
if (gvalid[p]) pop_heal_fast(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
|
||
if (svalid[p]) pop_heal_fast(sx_l[p], sy_l[p] + POP_YOFF, sw_l[p], sh_l[p]);
|
||
gvalid[p] = svalid[p] = 0;
|
||
}
|
||
|
||
void pop_guard_draw(void) __banked
|
||
{
|
||
int obj_x, obj_y, top, fwd, fp_x;
|
||
uint8_t page, idx, p, flip;
|
||
const uint8_t *img;
|
||
uint16_t w, h;
|
||
/* Окно fore-клипа — по ОБЪЕДИНЁННОМУ габариту «страж + клинок»; сам
|
||
* футпринт (pop_fore_over_char) считается по спрайту стража, поэтому
|
||
* w/h ниже НЕ трогаем. */
|
||
int clip_x = 0, clip_y = 0; uint16_t clip_w = 0, clip_h = 0;
|
||
|
||
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);
|
||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip, 0, 0);
|
||
else
|
||
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;
|
||
g_last_w = w; /* для char_width_half, см. выше */
|
||
clip_x = bx; clip_y = top; clip_w = w; clip_h = h;
|
||
}
|
||
/* Клинок стража — тот же chtab_0, что у Кида (add_sword_to_objtable,
|
||
* seg006:1798): у ЖИВОГО стража меч в руке всегда. Свой прямоугольник
|
||
* heal; окно fore-клипа — объединение (без него клинок полез бы поверх
|
||
* переднего столба). */
|
||
{
|
||
int sx, sy; uint16_t sw, sh;
|
||
if (pop_sword_draw(&Guard, &pop_gframe, obj_x, obj_y,
|
||
&sx, &sy, &sw, &sh)) {
|
||
p = gfx_get_draw_page() & 1;
|
||
sx_l[p] = sx; sy_l[p] = sy; sw_l[p] = sw; sh_l[p] = sh;
|
||
svalid[p] = 1;
|
||
if (!clip_w) {
|
||
clip_x = sx; clip_y = sy; clip_w = sw; clip_h = sh;
|
||
} else {
|
||
int x1 = clip_x + (int)clip_w, y1 = clip_y + (int)clip_h;
|
||
if (sx < clip_x) clip_x = sx;
|
||
if (sy < clip_y) clip_y = sy;
|
||
if (sx + (int)sw > x1) x1 = sx + (int)sw;
|
||
if (sy + (int)sh > y1) y1 = sy + (int)sh;
|
||
clip_w = (uint16_t)(x1 - clip_x);
|
||
clip_h = (uint16_t)(y1 - clip_y);
|
||
}
|
||
}
|
||
}
|
||
gfx_w0_unmap();
|
||
/* Окно fore-слоя = прямоугольник стража с клинком (см. pop_bg.c,
|
||
* fclip_on): без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов. */
|
||
pop_fore_set_clip(clip_x, clip_y + POP_YOFF, (int)clip_w, (int)clip_h);
|
||
/* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись
|
||
* midtable, а foretable рисуется после всех midtable-записей, поэтому
|
||
* колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid
|
||
* (seg008:690 draw_tile_fore). Без этого страж виден сквозь передний
|
||
* столб. */
|
||
if (w && h)
|
||
pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction);
|
||
}
|
||
|
||
/* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- *
|
||
* Оригинал рисует их в статус-полосе на y = 194 (ниже поля тайлов, внутри
|
||
* тех же 200 строк экрана PoP): HP Кида слева направо шагом 7, HP стража
|
||
* справа налево от x = 314. Пустые деления у Кида — свой спрайт (chtab_2
|
||
* id 217); у стража оригинал печатает тот же спрайт ЧЁРНЫМ силуэтом, а мы
|
||
* просто не печатаем — полоса и так на чёрном фоне.
|
||
*
|
||
* Спрайты Кида (216/217) лежат в его же атласах, поэтому блитим через
|
||
* pop_kid_img_blit; спрайт стража — image 0 его chtab (наша страница 0). */
|
||
#define HP_Y 194
|
||
#define HP_STEP 7
|
||
#define HP_KID_FULL 216
|
||
#define HP_KID_EMPTY 217
|
||
#define HP_MAXDRAW 10 /* больше в полосу всё равно не влезает */
|
||
|
||
/* Полоса перерисовывается ТОЛЬКО когда числа изменились — и тогда на ОБЕИХ
|
||
* страницах дабл-буфера (иначе на второй останется старое значение и его
|
||
* будет видно как мерцание через кадр). Отсюда счётчик страниц, а не флаг.
|
||
*
|
||
* Снимки прошлых значений пишем ДО сравнения: `if (n != s) s = n;` SDCC z80
|
||
* собирает неверно — SUB затирает A, и в s уезжает разность (memory
|
||
* sdcc_z80_cmp_store_a_bug). */
|
||
static uint8_t hp_todo; /* сколько страниц ещё обновить */
|
||
static uint8_t hp_kid_prev, hp_kidmax_prev, hp_gd_prev, hp_gdmax_prev;
|
||
|
||
/* Заставить перерисовать полосу на обеих страницах: звать при входе в
|
||
* комнату (фон перерисован целиком и стёр её) и при старте. */
|
||
void pop_hp_invalidate(void) __banked { hp_todo = 2; }
|
||
|
||
void pop_hp_draw(void) __banked
|
||
{
|
||
uint8_t i, n;
|
||
uint8_t gd = (uint8_t)(Guard.charid ? guardhp_curr : 0);
|
||
uint8_t gm = (uint8_t)(Guard.charid ? guardhp_max : 0);
|
||
|
||
{ /* Изменилось хоть одно число — обновить обе страницы. */
|
||
uint8_t k = hitp_curr, km = hitp_max;
|
||
uint8_t changed = (uint8_t)(k != hp_kid_prev || km != hp_kidmax_prev ||
|
||
gd != hp_gd_prev || gm != hp_gdmax_prev);
|
||
hp_kid_prev = k; hp_kidmax_prev = km;
|
||
hp_gd_prev = gd; hp_gdmax_prev = gm;
|
||
if (changed) hp_todo = 2;
|
||
}
|
||
if (hp_todo == 0) return;
|
||
hp_todo--;
|
||
|
||
/* Стереть прошлую полосу: фон под ней — чёрный борт. */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
bar(0, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6);
|
||
|
||
/* Кид: сначала пустые деления, поверх — полные (порядок оригинала). */
|
||
n = (uint8_t)(hitp_max > HP_MAXDRAW ? HP_MAXDRAW : hitp_max);
|
||
for (i = hitp_curr; i < n; i++)
|
||
pop_kid_img_blit(HP_KID_EMPTY, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||
for (i = 0; i < hitp_curr && i < HP_MAXDRAW; i++)
|
||
pop_kid_img_blit(HP_KID_FULL, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||
|
||
/* Страж: только полные деления, справа налево. Скелет полосы не имеет. */
|
||
if (g_ok && Guard.charid != 0 && Guard.charid != CHARID_4_SKELETON && gd) {
|
||
const uint8_t *img = (const uint8_t *)atlas_image(&gp[0], 0);
|
||
uint16_t w, h;
|
||
gfx_w0_map(gp[0].page);
|
||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||
if (w && h) {
|
||
/* Атлас стража тоже column-major — блит колоночный. */
|
||
n = (uint8_t)(gd > HP_MAXDRAW ? HP_MAXDRAW : gd);
|
||
for (i = 0; i < n; i++)
|
||
gfx_blit_cols_part_noclip(314 - (int)i * HP_STEP,
|
||
HP_Y + POP_YOFF, img, 0, 0, 0);
|
||
}
|
||
gfx_w0_unmap();
|
||
}
|
||
}
|