/* * scene.c — реализация покадрового раннера (см. scene.h). */ #include #include #include "tcheck.h" #include "pop_kid.h" #include "pop_map.h" #include "pop_ctrl.h" #include "pop_geom.h" #include "pop_guard.h" #include "kid_data.h" #include "scene.h" #include "stubs.h" #include "kidblob.h" sc_rec sc_trace[SC_TRACE_MAX]; uint8_t sc_len; static uint8_t sc_fg[30]; /* Соседей у сцены нет (links = 0) — по краям комнаты стена: карта сама * решает так, когда соседняя комната не задана. */ void sc_init(void) { kid_blob_install(); /* таблицы туда, куда смотрит pop_kid */ pop_kid_data_load("KID\\KID_DATA.BIN"); pop_kid_hp_newgame(); } void sc_room(const uint8_t *fg, uint8_t room) { uint8_t i; for (i = 0; i < 30; i++) { sc_fg[i] = fg[i]; tk_modif[i] = 0; } pop_map_set(sc_fg); pop_map_set_room(room); /* Изолированная сцена: соседей нет. Тип TILE_WALL (20) с обеих сторон * и сверху/снизу — тогда решения физики зависят только от sc_fg, и * трасса не поедет, если когда-нибудь поменяется загрузка кромок. */ pop_map_set_edges(0, 0, 0, 0); tk_start_room = room; tk_log_clear(); } void sc_kid_at(uint8_t col, uint8_t row, int8_t dir) { kid_init(2 /* seq_2_stand */, (int8_t)col, (int8_t)row, dir); pop_kid_hp_reset(); } void sc_kid_at_x(uint8_t col, uint8_t row, uint8_t x, int8_t dir) { kid_init(2 /* seq_2_stand */, (int8_t)col, (int8_t)row, dir); Kid.x = x; /* тайл задаёт ряд, X — фазу внутри тайла */ /* Колонку пересчитываем В ОКНЕ Char: determine_col — часть физики, а та * работает с активным персонажем (порт load_fram_det_col). */ pop_loadkid(); pop_determine_col(); pop_savekid(); pop_kid_hp_reset(); } /* ---- слот Guard ------------------------------------------------------ */ void sc_guard_at(uint8_t charid, uint8_t col, uint8_t row, int8_t dir, uint8_t hp) { /* Поля — как в enter_guard (seg002:0112): X из колонки, Y с пола ряда. */ Guard.charid = charid; Guard.curr_col = (int8_t)col; Guard.curr_row = (int8_t)row; Guard.x = (uint8_t)(pop_x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX); Guard.y = (uint8_t)pop_y_land[row + 1]; Guard.direction = dir; Guard.alive = -1; Guard.action = 1; /* actions_1_run_jump */ Guard.sword = 2; /* у живого стража меч в руке */ Guard.fall_x = Guard.fall_y = 0; Guard.repeat = 0; Guard.room = 1; guardhp_max = guardhp_curr = hp; guardhp_delta = 0; /* Стойка с мечом (seq_77 у неактивного стража — но нам нужна поза, из * которой персонаж просто стоит и может упасть). */ pop_loadshad(); pop_char_set_seq(77 /* seq_77_guard_stand_inactive */); play_seq(); pop_saveshad(); } void sc_guard_run(uint8_t n) { uint8_t i; for (i = 0; i < n; i++) { /* play_guard_frame (seg000:1246) без ИИ: окно, кадр, seq, физика. */ pop_loadshad_and_opp(); pop_load_fram_det_col(); play_seq(); pop_saveshad_and_opp(); pop_guard_phys_tick(); } } void sc_trace_clear(void) { sc_len = 0; } static void sc_record(void) { sc_rec *r; if (sc_len >= SC_TRACE_MAX) return; r = &sc_trace[sc_len++]; r->frame = Kid.frame; r->x = Kid.x; r->y = Kid.y; r->dir = Kid.direction; r->col = Kid.curr_col; r->row = Kid.curr_row; r->action = Kid.action; r->seq = (uint16_t)(Kid.curr_seq - SEQTBL_BASE); } uint8_t sc_run(uint8_t keys, uint8_t n) { uint8_t i, done = 0; for (i = 0; i < n; i++) { tk_keys_clear(); if (keys & SC_L) tk_key_down(KBD_LEFT); if (keys & SC_R) tk_key_down(KBD_RIGHT); if (keys & SC_U) tk_key_down(KBD_UP); if (keys & SC_D) tk_key_down(KBD_DOWN); if (keys & SC_SHIFT) tk_key_down(KBD_LSHIFT); /* Порядок ровно как в главном цикле roomtest.c (kid_heal и * отрисовка выпадают — они ничего не решают). pop_loose_tick * оставлен: он не рисование, а МЕХАНИКА — досчитывает тряску и * убирает провалившуюся плиту из карты, то есть меняет то, по чему * физика в следующем кадре принимает решения. */ pop_ctrl_tick(); kid_tick(); pop_phys_tick(); pop_loose_tick(); sc_record(); done++; if (sc_len >= SC_TRACE_MAX) break; } return done; } /* ---- сверка с эталоном ---------------------------------------------- */ void sc_expect(const uint8_t *golden, uint8_t nrec) { uint8_t i; const uint8_t *g = golden; tc_check((int16_t)sc_len, (int16_t)nrec, "sc_len", __LINE__); for (i = 0; i < nrec && i < sc_len; i++, g += SC_REC_BYTES) { const sc_rec *r = &sc_trace[i]; /* Номер кадра идёт первым полем каждой проверки: в отчёте видно, * на каком именно кадре трасса разошлась. */ tc_check((int16_t)r->frame, (int16_t)g[0], "frame", i); tc_check((int16_t)r->x, (int16_t)g[1], "x", i); tc_check((int16_t)r->y, (int16_t)g[2], "y", i); tc_check((int16_t)r->dir, (int16_t)(int8_t)g[3], "dir", i); tc_check((int16_t)r->col, (int16_t)(int8_t)g[4], "col", i); tc_check((int16_t)r->row, (int16_t)(int8_t)g[5], "row", i); tc_check((int16_t)r->action, (int16_t)g[6], "action", i); tc_check((int16_t)r->seq, (int16_t)((uint16_t)g[7] | ((uint16_t)g[8] << 8)), "seq", i); } } /* ---- снятие эталона -------------------------------------------------- */ static void put(char c) { if (tc_result.logn < TC_LOGSZ) tc_result.log[tc_result.logn++] = (uint8_t)c; } static void put_byte(uint8_t v) { static const char hex[] = "0123456789ABCDEF"; put(hex[v >> 4]); put(hex[v & 15]); } void sc_dump(const char *name) { uint8_t i; tc_result.magic = TC_MAGIC; tc_result.total = 1; tc_result.failed = 1; /* иначе раннер лог не напечатает */ put('#'); put('#'); put('#'); put(' '); while (*name) put(*name++); put('\n'); for (i = 0; i < sc_len; i++) { const sc_rec *r = &sc_trace[i]; put_byte(r->frame); put_byte(r->x); put_byte(r->y); put_byte((uint8_t)r->dir); put_byte((uint8_t)r->col); put_byte((uint8_t)r->row); put_byte(r->action); put_byte((uint8_t)r->seq); put_byte((uint8_t)(r->seq >> 8)); put('\n'); } }