Files
Sprinter-SDCC/applications/PoP/roomtest/pop_kid.c
T
snark13 8b30dc20c8 roomtest: loose-полы — тряска (knock), падение с окклюзией, фикс дабл-буфера
Проваливающиеся полы в roomtest (порт SDLPoP seg007/seg008), проверено в MAME:

- Падающий кусок (mob): правый край env-42 (w=26, доходит до mob_x+57)
  теперь полностью покрыт heal-коридором (MOB_W 56->64) — убран тёмный
  хвост-тень на полу 2,7 ПОСЛЕ падения.
- Окклюзия правого куска во ВРЕМЯ падения: после отрисовки плиты
  перерисовывается сосед-пол draw_tile(row,col+1) в том же кадре — край
  прячется за полом 2,7 (порт redraw_at_cur_mob: set_redraw_full+1).
- Тряска от сотрясения (knock): seqtbl-команды KNOCK_DOWN/UP в play_seq ->
  флаг knock -> do_knock(ряд) трясёт loose ряда из покоя (modif=0x80).
  KNOCK_DOWN в land-seq (приземление) и runcyc (footstep).
- Фикс «бесконечной тряски» (дабл-буфер): при завершении тряски (0x84->0)
  перерисовать покойный кадр плиты на ОБЕИХ страницах (loose_rest, 2 кадра) —
  иначе на одной странице застревает дрожащий кадр правой грани (живёт в
  тайлах col и col+1) -> мерцание через флип.

Инфраструктура/документация:
- app.mk: цель `make hdd` (упаковка в D: для MCP-моста MAME).
- docs: POP-DAT-FormatSpecifications.pdf/.txt как каноническая спецификация
  форматов ресурсов; ссылки в README/MSDOS_RESOURCE_FORMAT.
- README.md/CLAUDE.md для applications/PoP и roomtest (правило «SDLPoP —
  источник истины», порядок слоёв, режим отладки freeze 1/2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:27:47 +03:00

252 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
#include "kid_data.h"
kid_t Kid;
/* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN
* в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */
int8_t knock;
/* Координатные таблицы (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: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */
knock = 1;
break;
case 0xF4: /* KNOCK_DOWN: приземление трясёт loose В СВОЁМ РЯДУ */
knock = -1;
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
/* Прямоугольник последней отрисовки Kid — ПО СТРАНИЦЕ (0/1). У каждой
* графической страницы своя ОЗУ-копия/видео-ОЗУ, поэтому heal должен
* стирать пиксели Kid именно той страницы, в которую сейчас рисуем
* (при дабл-буфере страница чередуется — прошлый Kid на этой странице
* был 2 логических кадра назад). В однобуфере используется только [0]. */
static int kid_lx[2], kid_ly[2];
static uint16_t kid_lw[2], kid_lh[2];
static uint8_t kid_lvalid[2];
/* Метрики последнего kid_draw для футпринта fore-over-Kid (pop_bg):
* obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */
static int kid_fp_x, kid_fp_y;
static uint16_t kid_fp_w, kid_fp_h;
int kid_fp_obj_x(void) { return kid_fp_x; }
int kid_fp_obj_y(void) { return kid_fp_y; }
uint16_t kid_fp_width(void) { return kid_fp_w; }
uint16_t kid_fp_height(void) { return kid_fp_h; }
/* Восстановить фон из теневой копии на месте ПРЕДЫДУЩЕГО кадра Kid. */
void kid_heal(void)
{
uint8_t p = gfx_get_draw_page();
if (kid_lvalid[p])
gfx_heal(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]); /* +YOFF: центрирование */
kid_lvalid[p] = 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 (в ЛОГИЧЕСКОЙ координате) */
kid_fp_x = obj_x; /* ЛОГИЧЕСКАЯ X (до ×8/7) — для футпринта fore */
kid_fp_y = obj_y;
/* 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;
uint8_t dp = gfx_get_draw_page(); /* в какую страницу рисуем */
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
gfx_blit_cols(bx, top + POP_YOFF, img, flip); /* +YOFF: центрирование */
gfx_set_bank(GFX_BANK_NORMAL);
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
* узко, только по прямоугольнику Кида и только при выходе. */
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
kid_lx[dp] = bx; kid_ly[dp] = top;
kid_lw[dp] = w; kid_lh[dp] = h; kid_lvalid[dp] = 1;
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
}
gfx_w0_unmap();
}