Files
snark13 cd8d566d82 applications/PoP: порт Prince of Persia — PoC (roomtest) + пайплайн
Порт 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>
2026-07-17 17:51:08 +03:00

159 lines
6.3 KiB
C
Raw Permalink 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.
/*
* room.c — вся логика комнаты, ОБЫЧНЫЙ HOME-файл (без --bank).
*
* 2026-07-15/16: изначально была банковская (--bank 1), сначала в W3
* (huge-дефолт), потом пробовали W1 (--memory big) — оба варианта
* упирались в проблемы (W3 конфликтует с gfx_blit_part/atlas_load,
* которые сами трогают W3; W1-банк несовместим с CODE=W1 — трамплин
* сам бы уехал из W1; --memory big тесно — CODE+DATA+BSS ~20КБ, не
* лезет в один 16КБ). После замены sprintf() на ручное hex-форматирование
* пути (экономия ~2.9КБ — sprintf/vsprintf тянули за собой заметный
* кусок форматирующего кода, единственный потребитель в HOME) всё
* снова влезает в W1 обычным HOME-кодом — банк ОКАЗАЛСЯ НЕ НУЖЕН.
* Так проще: gfx_blit_part()/atlas_load() (трогают W3 транзитивно)
* теперь просто обычные HOME-функции, без всяких W1/W3-танцев.
*
* gfx_blit_part пишет ПРЯМО в текущий банк (видео-ОЗУ + теневая копия
* при GFX_BANK_NORMAL) — перманентно, не через sprite_t. Вызывающий
* код обязан позвать room_draw() на КАЖДУЮ из двух страниц дабл-буфера
* при входе в комнату, иначе heal спрайтов увидит фон только на одной
* странице.
*/
#include <stdio.h>
#include <gfx.h>
#include "room.h"
/* 32x64 на тайл: 3*64+8=200 — сходится с разрешением DOS 320x200
* (8px внизу — под полоску жизней/строку текста, не часть игрового
* поля). Уточнить точнее, когда будут готовы настоящие ассеты. */
uint8_t room_row_h[ROOM_ROWS] = {64, 64, 64};
tile_atlas_t tile_atlas[32];
int tile_atlas_load(const char *dir)
{
static const char HEXD[] = "0123456789abcdef";
char path[64];
int i, count = 0;
for (i = 0; i < 32; i++) {
/* Вручную, не sprintf("%stile%02x.atl",...) — единственный
* потребитель printf-семейства в этом приложении, тянул за
* собой ~2.9КБ форматирующего кода; формат тут фиксированный
* (2 hex-цифры). */
char *p = path;
const char *s = dir;
while (*s) *p++ = *s++;
s = "tile";
while (*s) *p++ = *s++;
*p++ = HEXD[(i >> 4) & 0xF];
*p++ = HEXD[i & 0xF];
s = ".atl";
while (*s) *p++ = *s++;
*p = '\0';
if (atlas_load(&tile_atlas[i].at, path) == 0) {
tile_atlas[i].loaded = 1;
count++;
} else {
tile_atlas[i].loaded = 0;
}
}
return count;
}
void tile_atlas_free(void)
{
int i;
for (i = 0; i < 32; i++) {
if (tile_atlas[i].loaded) {
atlas_free(&tile_atlas[i].at);
tile_atlas[i].loaded = 0;
}
}
}
int level_load_room(level_t *lv, uint8_t room, const char *path)
{
FILE *f = fopen(path, "rb");
uint8_t buf[ROOM_TILES * 2 + 3]; /* fore[30]+back[30]+start_room/pos/dir */
int i;
if (!f)
return -1;
if (fread(buf, 1, sizeof(buf), f) != sizeof(buf)) {
fclose(f);
return -1;
}
fclose(f);
for (i = 0; i < ROOM_TILES; i++) {
tile_t *t = level_tile(lv, room, (uint8_t)(i % ROOM_COLS),
(uint8_t)(i / ROOM_COLS));
t->type = buf[i];
t->state = buf[ROOM_TILES + i];
}
lv->start_room = buf[ROOM_TILES * 2];
lv->start_location = buf[ROOM_TILES * 2 + 1];
lv->start_direction = (int8_t)buf[ROOM_TILES * 2 + 2];
return 0;
}
/* _bgi_blit_rows_raw держит ОДИН DI на весь блит (см. её шапку) —
* вызывающий обязан резать блиты выше 16 строк на чанки, иначе окно
* DI слишком длинное (риск сорвать кадровую цепочку IRQ/CBL). Наши
* тайлы 64px высотой — режем на чанки по CHUNK_H строк. */
#define CHUNK_H 16
void room_draw(level_t *lv, uint8_t room)
{
int row, col, y = 0;
for (row = 0; row < ROOM_ROWS; row++) {
int x = 0;
uint8_t fh = room_row_h[row];
for (col = 0; col < ROOM_COLS; col++) {
tile_t *t = level_tile(lv, room, (uint8_t)col, (uint8_t)row);
uint8_t type = TILE_TYPE(t->type);
if (tile_atlas[type].loaded) {
uint8_t count = tile_atlas[type].at.count;
uint8_t variant = 0;
uint8_t is_var;
if (count != 0) {
switch (type) {
case TILE_SPIKES:
case TILE_GATE:
case TILE_TAPESTRY:
case TILE_TAPESTRY_TOP:
case TILE_CHOPPER:
case TILE_POTION:
is_var = 1;
break;
default:
is_var = 0;
}
variant = is_var
? (uint8_t)(t->state % count)
: (uint8_t)((room * 31u + col * 7u + row * 13u) % count);
}
const void *img = atlas_image(&tile_atlas[type].at, 0);
uint8_t sub_y;
gfx_w0_map(tile_atlas[type].at.page);
for (sub_y = 0; sub_y < fh; sub_y = (uint8_t)(sub_y + CHUNK_H)) {
uint8_t chunk = (uint8_t)(fh - sub_y);
if (chunk > CHUNK_H)
chunk = CHUNK_H;
gfx_blit_part(x, y + sub_y, img, variant * ROOM_TILE_W,
sub_y, ROOM_TILE_W, chunk);
}
gfx_w0_unmap();
}
x += ROOM_TILE_W;
}
y += fh;
}
}