/* pop_timer — логические тики, pause/unlimited, читы и QuickSave state. */ #include #include "tcheck.h" #include "pop_timer.h" /* Минимальный memory-stream вместо EMM. pop_timer_qsave использует ровно * u8/u16, поэтому этот stub одновременно проверяет его endian-контракт. */ static uint8_t stream[3]; static uint8_t stream_pos; void pop_qs_u8(pop_qs_io_t *io, uint8_t *value) { if (io->reading) { if (io->apply) *value = stream[stream_pos]; } else { stream[stream_pos] = *value; } ++stream_pos; } void pop_qs_u16(pop_qs_io_t *io, uint16_t *value) { uint8_t lo, hi; if (io->reading) { lo = stream[stream_pos++]; hi = stream[stream_pos++]; if (io->apply) *value = (uint16_t)(lo | ((uint16_t)hi << 8)); } else { stream[stream_pos++] = (uint8_t)*value; stream[stream_pos++] = (uint8_t)(*value >> 8); } } TC_TEST(timer_starts_and_only_runs_when_allowed) { pop_timer_new_game(); TC_EQ(pop_timer_minutes, POP_TIMER_START_MINUTES); TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); TC_FALSE(pop_timer_tick(0, 1, 4, 4)); /* Settings: unlimited */ TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); TC_FALSE(pop_timer_tick(1, 0, 4, 4)); /* pause/HDD/cutscene */ TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); pop_timer_minutes = 2; pop_timer_ticks = 2; TC_FALSE(pop_timer_tick(1, 1, 4, 4)); TC_EQ(pop_timer_minutes, 2); TC_EQ(pop_timer_ticks, 1); TC_FALSE(pop_timer_tick(1, 1, 4, 4)); TC_EQ(pop_timer_minutes, 1); TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); } TC_TEST(timer_expiry_is_one_shot) { pop_timer_minutes = 1; pop_timer_ticks = 1; TC_TRUE(pop_timer_tick(1, 1, 4, 4)); TC_EQ(pop_timer_minutes, 0); TC_EQ(pop_timer_ticks, POP_TIMER_START_TICKS); TC_FALSE(pop_timer_tick(1, 1, 4, 4)); TC_FALSE(pop_timer_tick(0, 1, 4, 4)); } TC_TEST(timer_cheats_match_sdlpop_rules) { pop_timer_minutes = 5; pop_timer_cheat_subtract(); TC_EQ(pop_timer_minutes, 4); pop_timer_minutes = 1; pop_timer_cheat_subtract(); TC_EQ(pop_timer_minutes, 1); /* minus не создаёт expiry */ pop_timer_minutes = 0; pop_timer_cheat_add(); TC_EQ(pop_timer_minutes, 1); pop_timer_minutes = 0xFF; pop_timer_cheat_add(); TC_EQ(pop_timer_minutes, 0xFF); /* не сворачиваемся в 0 */ } TC_TEST(timer_qsave_restores_exact_state_and_rejects_bad_tick) { pop_qs_io_t io; pop_timer_minutes = 42; pop_timer_ticks = 103; stream_pos = 0; io.reading = io.apply = io.failed = 0; pop_timer_qsave(&io); TC_EQ(stream_pos, 3); pop_timer_minutes = 7; pop_timer_ticks = 400; stream_pos = 0; io.reading = io.apply = 1; io.failed = 0; pop_timer_qsave(&io); TC_FALSE(io.failed); TC_EQ(pop_timer_minutes, 42); TC_EQ(pop_timer_ticks, 103); stream[0] = 9; stream[1] = 0; stream[2] = 0; /* tick 0 — impossible */ pop_timer_minutes = 33; pop_timer_ticks = 444; stream_pos = 0; io.failed = 0; pop_timer_qsave(&io); TC_TRUE(io.failed); TC_EQ(pop_timer_minutes, 33); TC_EQ(pop_timer_ticks, 444); } /* --- ХОД ЧАСОВ НЕ ЗАВИСИТ ОТ РЕЖИМА СКОРОСТИ ------------------------- * * * Тик стоит `base` кадров луча — столько, сколько их в кадре режима * NORMAL. Быстрые режимы только УКОРАЧИВАЮТ кадр (spent < base), поэтому * за одно и то же реальное время выходит одинаковое число тиков. * Наблюдение пользователя, из которого выросла правка: «при FAST/FASTEST * время начинает бежать быстрее». */ /* Сколько тиков насчитается за `frames` логических кадров темпа spent/base. */ static uint16_t ticks_over(uint8_t frames, uint8_t spent, uint8_t base) { uint16_t before; uint8_t i; pop_timer_new_game(); before = pop_timer_ticks; for (i = 0; i < frames; i++) pop_timer_tick(1, 1, spent, base); return (uint16_t)(before - pop_timer_ticks); } TC_TEST(timer_normal_ticks_every_frame) { /* NORMAL вне боя: кадр равен эталону — тик на каждом кадре, как было до * правки. Страховка, что NORMAL не изменился ни на тик. */ TC_EQ(ticks_over(12, 4, 4), 12); } TC_TEST(timer_normal_fight_ticks_every_frame) { /* NORMAL в бою: кадр длиннее (5), но и эталон 5 — часы по-прежнему идут * по кадру. В РЕАЛЬНОМ времени это медленнее, и ровно так же ведёт себя * оригинал, поэтому трогать нечего. */ TC_EQ(ticks_over(12, 5, 5), 12); } TC_TEST(timer_fast_keeps_real_time) { /* FAST вне боя: кадр 3 при эталоне 4. За 12 кадров (36 кадров луча) — * 9 тиков, ровно столько же, сколько NORMAL насчитает за те же 36 кадров * луча. До правки было бы 12: часы бежали на треть быстрее. */ TC_EQ(ticks_over(12, 3, 4), 9); } TC_TEST(timer_fastest_fight_keeps_real_time) { /* FASTEST в бою — самый короткий кадр против самого длинного эталона: 3 против * 5. За 20 кадров ждём 12 тиков. */ TC_EQ(ticks_over(20, 3, 5), 12); } void main(void) { TC_RUN(timer_starts_and_only_runs_when_allowed); TC_RUN(timer_expiry_is_one_shot); TC_RUN(timer_cheats_match_sdlpop_rules); TC_RUN(timer_qsave_restores_exact_state_and_rejects_bad_tick); TC_RUN(timer_normal_ticks_every_frame); TC_RUN(timer_normal_fight_ticks_every_frame); TC_RUN(timer_fast_keeps_real_time); TC_RUN(timer_fastest_fight_keeps_real_time); }