/*
 * pop_geom.c — общая геометрия + PRNG оригинала.  См. pop_geom.h.
 */
#include "pop_geom.h"

/* data.h: шаг 14 (ширина тайла в координатах персонажа), [5] = col 0. */
const int16_t pop_x_bump[20] = {
    -12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
    128, 142, 156, 170, 184, 198, 212, 226, 240, 254
};
const int16_t pop_y_land[5] = {-8, 55, 118, 181, 244};

int8_t pop_y_to_row(int16_t y)
{
    return (int8_t)((y + 60) / TILE_SIZEY % 4 - 1);
}

/* Уложить значение в диапазон 0..maxv.  Вызовы оригинала — prandom(1),
 * prandom(255), prandom(0xFF): делитель степень двойки, то есть маска, а не
 * деление.  __moduint на Z80 стоит заметно дороже проверки n & (n-1). */
static uint16_t pop_rnd_fit(uint16_t v, uint16_t maxv)
{
    uint16_t n = (uint16_t)(maxv + 1);
    if ((uint16_t)(n & (uint16_t)(n - 1)) == 0)
        return (uint16_t)(v & (uint16_t)(n - 1));
    return (uint16_t)(v % n);
}

#if POP_PRANDOM_EXACT
/* ---- LCG оригинала: s = s*214013 + 2531011 (seg009:321) ------------ *
 *
 * ЕДИНСТВЕННОЕ место в порте на ассемблере, и вот почему.  На Z80 нет
 * умножения, а SDCC для 32-битного `s * 214013` зовёт __mullong — 8 430
 * тактов на вызов (замер в MAME).  Написанное на C через 16-битные
 * половины это ~3 500.  Здесь ~1 000, при полностью той же
 * последовательности, что в SDLPoP.  Альтернатива «взять генератор
 * попроще» (8-битный RND Apple II, xorshift) экономит меньше и ломает
 * совместимость с эталоном, по которому сверяем картинку.
 *
 * Приём: 214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3 — схема Горнера по
 * РАЗРЕЖЕННОЙ записи константы.  Вместо 12 сложений (по числу единиц в
 * 0x343FD) — 17 удвоений, ТРИ сложения и одно вычитание.  Величина 3*s,
 * нужная в конце, попадается по дороге на втором шаге — её и сохраняем.
 *
 * ABI __sdcccall(1): arg1 (указатель на сид) -> HL, возврат -> DE.  IX не
 * используется, сохранять нечего.  Клоббер AF/BC/DE/HL.  Аккумулятор —
 * DE:HL (DE старшее слово); s.lo живёт в BC, s.hi и 3*s — в статиках
 * (регистров на всё не хватает).
 */
uint16_t pop_pr_ptr;                   /* адрес сида                    */
uint16_t pop_pr_sh;                    /* старшее слово исходного сида  */
uint16_t pop_pr_3s_lo, pop_pr_3s_hi;   /* 3*s, нужное в конце           */

static uint16_t pop_lcg_step(pop_rnd_t *seed) __naked
{
    (void)seed;
    __asm
        ld      (_pop_pr_ptr), hl
        ld      c, (hl)
        inc     hl
        ld      b, (hl)                ; BC = s.lo
        inc     hl
        ld      a, (hl)
        ld      (_pop_pr_sh), a
        inc     hl
        ld      a, (hl)
        ld      (_pop_pr_sh + 1), a    ; (pop_pr_sh) = s.hi

        ld      h, b
        ld      l, c
        ld      de, (_pop_pr_sh)       ; акк = s

        ;; акк = акк*2 + s  =>  3s
        add     hl, hl
        rl      e
        rl      d
        add     hl, bc
        ex      de, hl
        ld      a, (_pop_pr_sh)        ; LD A,(nn) флаги НЕ трогает
        adc     a, l
        ld      l, a
        ld      a, (_pop_pr_sh + 1)
        adc     a, h
        ld      h, a
        ex      de, hl
        ld      (_pop_pr_3s_lo), hl
        ld      (_pop_pr_3s_hi), de

        ;; акк = акк*4 + s  =>  13s
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, bc
        ex      de, hl
        ld      a, (_pop_pr_sh)
        adc     a, l
        ld      l, a
        ld      a, (_pop_pr_sh + 1)
        adc     a, h
        ld      h, a
        ex      de, hl

        ;; акк = акк*16 + s  =>  209s
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, bc
        ex      de, hl
        ld      a, (_pop_pr_sh)
        adc     a, l
        ld      l, a
        ld      a, (_pop_pr_sh + 1)
        adc     a, h
        ld      h, a
        ex      de, hl

        ;; акк *= 1024  =>  214016s   (10 удвоений)
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d
        add     hl, hl
        rl      e
        rl      d

        ;; акк -= 3s  =>  214013s
        ld      bc, (_pop_pr_3s_lo)
        or      a                      ; CY = 0
        sbc     hl, bc
        ld      bc, (_pop_pr_3s_hi)    ; LD BC,(nn) флаги НЕ трогает
        ex      de, hl
        sbc     hl, bc
        ex      de, hl

        ;; акк += 2531011 = 0x0026_9EC3
        ld      bc, #0x9EC3
        add     hl, bc
        ld      bc, #0x0026
        ex      de, hl
        adc     hl, bc
        ex      de, hl

        ;; сохранить сид, вернуть СТАРШЕЕ слово в DE
        push    de
        ex      de, hl                 ; DE = младшее слово
        ld      hl, (_pop_pr_ptr)
        ld      (hl), e
        inc     hl
        ld      (hl), d
        inc     hl
        pop     de                     ; DE = старшее слово
        ld      (hl), e
        inc     hl
        ld      (hl), d
        ret
    __endasm;
}

uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv)
{
    return pop_rnd_fit(pop_lcg_step(seed), maxv);
}

#else  /* !POP_PRANDOM_EXACT — генератор попроще, НЕ совместимый с эталоном */

/* xorshift16 + шаг Вейля.  Зачем не LCG оригинала: тот 32-битный, и даже
 * без __mullong (см. ветку выше) стоит ~7 000 тактов на вызов — при двух
 * вызовах за кадр это 3 % бюджета на генератор случайных чисел.  Здесь
 * умножений нет вовсе, только сдвиги и xor.
 *
 * Почему НЕ 8-битный RND Apple II (RNDseed := 5*RNDseed + 23 mod 256,
 * Prince-of-Persia-Apple-II): он ещё быстрее, но у LCG по модулю 256
 * младшие биты вырождены — бит 0 просто чередуется.  Наши вызовы это
 * видят: раскладка кладки берёт prandom(1) (один бит) и prandom(4), и
 * вместо шума получилась бы правильная шахматка.  У xorshift такой
 * структуры нет, а стоит он примерно столько же.
 *
 * Шаг Вейля (+0x9E37, нечётный) нужен ещё и потому, что у чистого
 * xorshift ноль — неподвижная точка, а сид кладки (номер комнаты + ряд +
 * колонка) вполне может оказаться нулём.
 *
 * ПОСЛЕДСТВИЕ: последовательность отличается от SDLPoP — другая (но
 * статистически такая же) раскладка кладки и другие броски в боёвке.
 * Поэтому по умолчанию собирается НЕ эта ветка: после переписывания LCG
 * на ассемблер выигрыш от смены генератора почти исчез, а сверять
 * картинку с эталоном стало важнее.  Ветка оставлена как запасной ход. */
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv)
{
    uint16_t s = seed->lo;
    s ^= (uint16_t)(s << 7);
    s ^= (uint16_t)(s >> 9);
    s ^= (uint16_t)(s << 8);
    s = (uint16_t)(s + 0x9E37u);
    seed->lo = s;
    seed->hi = 0;                    /* старшая половина не используется */
    /* Отдаём СТАРШИЙ байт: у него нет короткопериодической структуры
     * младших бит, а вызывающие берут от результата именно младшие. */
    return pop_rnd_fit((uint16_t)(s >> 8), maxv);
}

#endif
