cd8d566d82
Порт PoP на Sprinter. Текущий PoC — applications/PoP/roomtest/: комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре и коллизией с картой. - roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип, seqtbl-анимация), pop_ctrl (порт control() PoP на held-state kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены, падение/приземление, отскок seq_47, вертикальный прыжок K4.1). MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge). - toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract — распаковка res-графики MSDOS в атласы + композиция комнат. - toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD), png_strip.py / room_compose.py (ассет-пайплайн). - docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT). - bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип). .gitignore: build-артефакты applications/*/*/*; исключены внешние референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
221 lines
8.2 KiB
C
221 lines
8.2 KiB
C
/*
|
||
* pop_kid.c — движок Kid (Фаза K1): порт seg006.c play_seq/load_frame +
|
||
* seqtbl-интерпретатор. Данные — kid_data.h.
|
||
*/
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprite.h>
|
||
#include "pop_kid.h"
|
||
#include "kid_data.h"
|
||
|
||
kid_t Kid;
|
||
|
||
/* Координатные таблицы (data.h). */
|
||
static const uint8_t x_bump[20] = {
|
||
(uint8_t)-12, 2, 16, 30, 44, 58, 72, 86, 100, 114,
|
||
128, 142, 156, 170, 184, 198, 212, 226, 240, 254
|
||
};
|
||
static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
|
||
#define FIRST_ONSCREEN_COLUMN 5
|
||
#define TILE_SIZEX 14
|
||
|
||
/* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */
|
||
#define KID_MAXPAGES 28
|
||
static atlas_t kidp[KID_MAXPAGES];
|
||
static uint8_t kid_npages;
|
||
|
||
int pop_kid_load(uint8_t npages)
|
||
{
|
||
static const char *const names[KID_MAXPAGES] = {
|
||
"kid0.atl","kid1.atl","kid2.atl","kid3.atl","kid4.atl","kid5.atl",
|
||
"kid6.atl","kid7.atl","kid8.atl","kid9.atl","kid10.atl","kid11.atl",
|
||
"kid12.atl","kid13.atl","kid14.atl","kid15.atl","kid16.atl","kid17.atl",
|
||
"kid18.atl","kid19.atl","kid20.atl","kid21.atl","kid22.atl","kid23.atl",
|
||
"kid24.atl","kid25.atl","kid26.atl","kid27.atl"
|
||
};
|
||
uint8_t i;
|
||
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
|
||
for (i = 0; i < npages; i++)
|
||
if (atlas_load(&kidp[i], names[i]) != 0)
|
||
return -1;
|
||
kid_npages = npages;
|
||
return 0;
|
||
}
|
||
|
||
void pop_kid_free(void)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < kid_npages; i++)
|
||
atlas_free(&kidp[i]);
|
||
kid_npages = 0;
|
||
}
|
||
|
||
/* ---- seqtbl-доступ (база SEQTBL_BASE) ------------------------------ */
|
||
#define SEQ(cs) kid_seqtbl[(uint16_t)((cs) - SEQTBL_BASE)]
|
||
|
||
static uint8_t char_dx_forward(int8_t dx)
|
||
{
|
||
if (Kid.direction < 0) dx = (int8_t)-dx;
|
||
return (uint8_t)(Kid.x + dx);
|
||
}
|
||
|
||
/* Аксессоры к текущему кадру (для pop_map — без дублирования kid_frames). */
|
||
int8_t kid_cur_dx(void) { return kid_frames[Kid.frame].dx; }
|
||
uint8_t kid_cur_flags(void) { return kid_frames[Kid.frame].flags; }
|
||
|
||
/* seqtbl_offset_char: сменить последовательность Kid (единая точка —
|
||
* pop_ctrl/pop_map зовут ЕЁ, а не дублируют kid_seq_off/kid_seqtbl). */
|
||
void kid_set_seq(uint8_t seq_id) { Kid.curr_seq = kid_seq_off[seq_id]; }
|
||
|
||
/* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0). */
|
||
void play_seq(void)
|
||
{
|
||
for (;;) {
|
||
uint8_t cmd = SEQ(Kid.curr_seq);
|
||
Kid.curr_seq++;
|
||
switch (cmd) {
|
||
case 0xFB: /* DX */
|
||
Kid.x = char_dx_forward((int8_t)SEQ(Kid.curr_seq));
|
||
Kid.curr_seq++;
|
||
break;
|
||
case 0xFA: /* DY */
|
||
Kid.y = (uint8_t)(Kid.y + (int8_t)SEQ(Kid.curr_seq));
|
||
Kid.curr_seq++;
|
||
break;
|
||
case 0xFE: /* FLIP */
|
||
Kid.direction = (int8_t)~Kid.direction;
|
||
break;
|
||
case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */
|
||
Kid.curr_seq += 2;
|
||
break;
|
||
case 0xFF: { /* JMP */
|
||
uint16_t a = (uint16_t)(SEQ(Kid.curr_seq) |
|
||
((uint16_t)SEQ((uint16_t)(Kid.curr_seq + 1)) << 8));
|
||
Kid.curr_seq = a;
|
||
break;
|
||
}
|
||
case 0xFD: /* UP */
|
||
Kid.curr_row--;
|
||
break;
|
||
case 0xFC: /* DOWN */
|
||
Kid.curr_row++;
|
||
break;
|
||
case 0xF9: /* ACTION */
|
||
Kid.action = SEQ(Kid.curr_seq);
|
||
Kid.curr_seq++;
|
||
break;
|
||
case 0xF8: /* SET_FALL */
|
||
Kid.fall_x = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++;
|
||
Kid.fall_y = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++;
|
||
break;
|
||
case 0xF5: case 0xF4: /* KNOCK up/down */
|
||
break;
|
||
case 0xF2: /* SOUND — потребить 1 байт */
|
||
Kid.curr_seq++;
|
||
break;
|
||
case 0xF1: /* END_LEVEL */
|
||
break;
|
||
case 0xF3: /* GET_ITEM — потребить 1 байт */
|
||
Kid.curr_seq++;
|
||
break;
|
||
case 0xF6: /* DIE nop */
|
||
break;
|
||
default: /* КАДР */
|
||
Kid.frame = cmd;
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir)
|
||
{
|
||
Kid.curr_col = col;
|
||
Kid.curr_row = row;
|
||
Kid.direction = dir;
|
||
Kid.x = (uint8_t)(x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
||
Kid.y = (uint8_t)y_land[row + 1];
|
||
Kid.fall_x = Kid.fall_y = 0;
|
||
Kid.repeat = 0;
|
||
Kid.curr_seq = kid_seq_off[seq_id];
|
||
play_seq(); /* до первого кадра */
|
||
}
|
||
|
||
void kid_tick(void)
|
||
{
|
||
play_seq();
|
||
}
|
||
|
||
/* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy
|
||
* (seg008.c:1728 load_frame_to_obj). НИЗ спрайта на obj_y, левый край
|
||
* на obj_x. Калибровка XOFF/YOFF — сверить с эталоном.
|
||
*
|
||
* Зеркалирование (K2a): спрайты Kid хранятся ЛИЦОМ ВЛЕВО (как в оригинале).
|
||
* seg008.c:864 — флип, когда персонаж смотрит ВПРАВО (direction==dir_0_right
|
||
* ==0), и тогда xpos -= image->w (draw_mid). Реализуем бесплатным
|
||
* горизонтальным флипом column-major спрайта (gfx_blit_cols flip=1):
|
||
* зеркало происходит ВНУТРИ [x,x+w), поэтому при флипе подаём x=obj_x-w,
|
||
* чтобы «hot-point» кадра остался на месте.
|
||
*
|
||
* Kid рисуется банком GFX_BANK_SPRITE (только видео-ОЗУ, НЕ теневая копия
|
||
* — 0xFF прозрачен), чтобы kid_heal() мог восстановить чистый фон из
|
||
* теневой копии (фон запёкся в неё при pop_room_draw). Прямоугольник
|
||
* последней отрисовки запоминается для heal. */
|
||
#define KID_XOFF 0
|
||
#define KID_YOFF 0
|
||
|
||
static int kid_lx, kid_ly;
|
||
static uint16_t kid_lw, kid_lh;
|
||
static uint8_t kid_lvalid;
|
||
|
||
/* Восстановить фон из теневой копии на месте ПРЕДЫДУЩЕГО кадра Kid. */
|
||
void kid_heal(void)
|
||
{
|
||
if (kid_lvalid)
|
||
gfx_heal(kid_lx, kid_ly, kid_lw, kid_lh);
|
||
kid_lvalid = 0;
|
||
}
|
||
|
||
void kid_draw(void)
|
||
{
|
||
kframe fr = kid_frames[Kid.frame];
|
||
int fwd, obj_x, obj_y, top;
|
||
uint8_t page, idx;
|
||
const uint8_t *img;
|
||
uint16_t w, h;
|
||
|
||
if (fr.image == 255)
|
||
return; /* пустой кадр */
|
||
fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx);
|
||
obj_x = (fwd << 1) - 116;
|
||
obj_y = (int)Kid.y + fr.dy;
|
||
if ((int8_t)(fr.flags ^ Kid.direction) >= 0)
|
||
obj_x++; /* even/odd pixel (в ЛОГИЧЕСКОЙ координате) */
|
||
/* seg008 calc_screen_x_coord: логическая X (280 шир = 28px/тайл) ->
|
||
* экран (320 шир = 32px/тайл), ×320/280 = ×8/7. БЕЗ этого Kid уезжает
|
||
* влево от тайлов тем сильнее, чем правее (col9 ~36px). Коллизия — в
|
||
* логической системе (не масштабируется). */
|
||
obj_x = obj_x * 8 / 7;
|
||
|
||
page = (uint8_t)(fr.image >> 3);
|
||
idx = (uint8_t)(fr.image & 7);
|
||
if (page >= kid_npages)
|
||
return; /* страница не загружена */
|
||
img = (const uint8_t *)atlas_image(&kidp[page], idx);
|
||
gfx_w0_map(kidp[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) {
|
||
uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */
|
||
int bx = obj_x + KID_XOFF;
|
||
if (flip)
|
||
bx -= (int)w; /* seg008: xpos -= image->w */
|
||
top = obj_y - (int)h + 1 + KID_YOFF;
|
||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
||
gfx_blit_cols(bx, top, img, flip);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
kid_lx = bx; kid_ly = top;
|
||
kid_lw = w; kid_lh = h; kid_lvalid = 1;
|
||
}
|
||
gfx_w0_unmap();
|
||
}
|