d438a1d3da
Меч в оригинале ОДИН на всех: и Кид, и страж рисуют клинок из chtab_0 (add_sword_to_objtable, seg006:1798). Раньше sword.atl содержал только кадры подъёма/ножен (sword_tbl 35..42), поэтому у стража меча не было видно вовсе. - pop_pack_kid.py пакует chtab_0 целиком (id 0..33, 5168 Б), индекс в атласе = id; - pop_extract_kid_data.py вытаскивает sword_tbl (53 строки) в kid_data.h макро-инициализаторами — таблица ложится в один TU, а не в каждый; - pop_sword_draw (pop_kid.c) — общая точка отрисовки клинка с полным условием оригинала (кадры 229..237 ИЛИ меч обнажён ИЛИ живой страж); зовут и kid_draw, и pop_guard_draw. Раздельный heal накладных спрайтов. Клинок и брызги урона раньше объединялись в один прямоугольник с персонажем, а объединение почти вдвое больше суммы двух (клинок уходит вперёд-вверх) — heal же стоит ровно по площади. Теперь у накладных свой прямоугольник и свой gfx_heal, а объединение осталось ТОЛЬКО для окна fore-клипа: там это 4 сравнения без рисования, но покрыть клинок обязано, иначе он полезет поверх столба. Отладка: DEBUG_SWORD_ROOM — Киду выдаётся меч при входе в комнату 3 (там страж), чтобы не бегать за ним в комнату 15. Бюджет (225 кадров, комната 3): 415 284 – 416 370 = 0.966–0.968 кадра. Против 384 168 – 384 636 до этого шага, то есть +31 700. Разложение по фазам (медианы): process_trobs 83 190, pop_guard_draw 92 483 (спрайт 28 763 + клинок 23 820 + fore_over_char 39 906), kid_draw 59 089, fore поверх Кида + борта 47 483, heal 40 068, логика 76 338, ввод 13 050. Видно, что клинок 21x8 стоит почти как спрайт стража — это фиксированные накладные расходы клипающего блита, а не пиксели; лечится noclip-путём для column-major (см. gfx_blit_noclip_fast). Отдельным шагом. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
152 lines
7.5 KiB
Python
152 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
pop_extract_kid_data.py — извлекает данные анимации Kid из исходников
|
||
SDLPoP в C-заголовок roomtest/kid_data.h (Фаза K1).
|
||
|
||
Извлекает:
|
||
frame_table_kid[] (seg006.c) -> kframe kid_frames[] {image,dx,dy,flags}
|
||
original_seqtbl[] (seqtbl.c) -> byte kid_seqtbl[] (2310 Б, база 0x196E)
|
||
original_seqtbl_offsets[] (seqtbl.c) -> word kid_seq_off[] (seq_id -> адрес)
|
||
|
||
seqtbl адресуется базой SEQTBL_BASE=0x196E: элемент = kid_seqtbl[addr-0x196E].
|
||
Автогенерация — чтобы не переписывать ~180 кадров + 2КБ байткода руками.
|
||
"""
|
||
import os
|
||
import re
|
||
|
||
SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "SDLPoP", "src")
|
||
OUT_BIN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||
"..", "poc", "res", "kid")
|
||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||
"..", "roomtest", "kid_data.h")
|
||
SEQTBL_BASE = 0x196E
|
||
|
||
|
||
def read(name):
|
||
return open(os.path.join(SRC, name)).read()
|
||
|
||
|
||
def parse_frame_table(txt, name="frame_table_kid"):
|
||
"""frame_table_kid[]/frame_tbl_guard[] -> список (image, dx, dy, flags).
|
||
Записи вида { IMG, 0xC0| 4, DX, DY, 0x40| 4 } — sword/flags как
|
||
OR-выражения."""
|
||
m = re.search(r'const frame_type ' + name + r'\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
||
body = m.group(1)
|
||
frames = []
|
||
for row in re.findall(r'\{([^}]*)\}', body):
|
||
parts = [p.strip() for p in row.split(',')]
|
||
image = int(parts[0], 0)
|
||
sword = eval(parts[1], {}, {}) # напр. "0x00| 9"
|
||
dx = int(parts[2], 0)
|
||
dy = int(parts[3], 0)
|
||
flags = eval(parts[4], {}, {})
|
||
frames.append((image & 0xFF, dx & 0xFF, dy & 0xFF, flags & 0xFF, sword & 0xFF))
|
||
return frames
|
||
|
||
|
||
def parse_sword_tbl(txt):
|
||
"""sword_tbl[] (seg006:2047) -> список (id, x, y). id 255 = клинка нет.
|
||
Индекс — sword_frame (cur_frame.sword & 0x3F); один и тот же chtab_0 и у
|
||
Кида, и у стража."""
|
||
m = re.search(r'const sword_table_type sword_tbl\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
||
rows = []
|
||
for row in re.findall(r'\{([^}]*)\}', m.group(1)):
|
||
a, b, c = [int(p.strip(), 0) for p in row.split(',')]
|
||
rows.append((a & 0xFF, b & 0xFF, c & 0xFF))
|
||
return rows
|
||
|
||
|
||
def parse_byte_array(txt, name):
|
||
m = re.search(r'const byte %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S)
|
||
return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))]
|
||
|
||
|
||
def parse_word_array(txt, name):
|
||
m = re.search(r'const word %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S)
|
||
return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))]
|
||
|
||
|
||
def emit_c(vals, per_line=12, fmt="0x%02X"):
|
||
out = []
|
||
for i in range(0, len(vals), per_line):
|
||
out.append(" " + ",".join(fmt % v for v in vals[i:i + per_line]) + ",")
|
||
return "\n".join(out)
|
||
|
||
|
||
def main():
|
||
seg006 = read("seg006.c")
|
||
seqc = read("seqtbl.c")
|
||
|
||
frames = parse_frame_table(seg006)
|
||
gframes = parse_frame_table(seg006, "frame_tbl_guard")
|
||
seqtbl = parse_byte_array(seqc, "original_seqtbl")
|
||
offs = parse_word_array(seqc, "original_seqtbl_offsets")
|
||
swtbl = parse_sword_tbl(seg006)
|
||
|
||
with open(OUT, "w") as f:
|
||
f.write("/* kid_data.h — данные анимации Kid (frame_table + seqtbl +\n"
|
||
" * offsets), извлечено toolchain/pop_extract_kid_data.py из\n"
|
||
" * SDLPoP. НЕ править вручную. seqtbl адресуется базой\n"
|
||
" * SEQTBL_BASE: kid_seqtbl[addr - SEQTBL_BASE]. */\n")
|
||
f.write("#ifndef KID_DATA_H\n#define KID_DATA_H\n#include <stdint.h>\n\n")
|
||
f.write("#define SEQTBL_BASE 0x%04Xu\n\n" % SEQTBL_BASE)
|
||
|
||
# frame table и seqtbl В .h НЕ ПОПАДАЮТ — они уезжают в kid_data.bin
|
||
# (EMM-страница): 3.5 КБ в _CODE окна W1/W2 нам дороже, чем один
|
||
# gfx_w0_map за тик. См. applications/PoP/docs/layout_plan_v2.md §5.1.
|
||
f.write("/* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid).\n"
|
||
" * САМА таблица кадров и seqtbl лежат в kid_data.bin (EMM-\n"
|
||
" * страница); здесь только тип и размеры. */\n")
|
||
f.write("typedef struct { uint8_t image; int8_t dx, dy; "
|
||
"uint8_t flags, sword; } kframe;\n")
|
||
f.write("#define KID_NFRAMES %d\n" % len(frames))
|
||
f.write("#define KID_SEQTBL_LEN %d\n\n" % len(seqtbl))
|
||
|
||
# раскладка kid_data.bin (смещения ОТ НАЧАЛА ДАННЫХ страницы)
|
||
f.write("/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */\n")
|
||
f.write("#define KID_BIN_FRAMES_OFF 0\n")
|
||
f.write("#define KID_BIN_SEQTBL_OFF %d\n" % (len(frames) * 5))
|
||
f.write("/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс\n"
|
||
" * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */\n")
|
||
f.write("#define KID_NGFRAMES %d\n" % len(gframes))
|
||
f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl)))
|
||
|
||
# seq offsets
|
||
f.write("#define KID_NSEQ %d\n" % len(offs))
|
||
f.write("static const uint16_t kid_seq_off[KID_NSEQ] = {\n")
|
||
f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n")
|
||
|
||
# sword_tbl — макро-инициализаторами, а не готовыми массивами: так
|
||
# 159 байт таблицы лягут в ОДИН TU (pop_kid.c), а не в каждый, кто
|
||
# включил заголовок.
|
||
f.write("/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.\n"
|
||
" * Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.\n"
|
||
" * Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord),\n"
|
||
" * x — «вперёд» по направлению персонажа. */\n")
|
||
f.write("#define KID_NSWORD %d\n" % len(swtbl))
|
||
for i, (nm, fmt) in enumerate((("ID", "0x%02X"), ("DX", "%d"), ("DY", "%d"))):
|
||
# dx/dy — знаковые: печатаем со знаком, иначе int8_t = overflow
|
||
vals = [r[i] if i == 0 else (r[i] - 256 if r[i] > 127 else r[i])
|
||
for r in swtbl]
|
||
f.write("#define KID_SWORD_%s_INIT { \\\n" % nm)
|
||
f.write(emit_c(vals, 12, fmt).replace("\n", " \\\n") + " \\\n}\n")
|
||
f.write("\n#endif\n")
|
||
|
||
# kid_data.bin: кадры (по 5 байт) + seqtbl подряд
|
||
os.makedirs(OUT_BIN_DIR, exist_ok=True)
|
||
binpath = os.path.join(OUT_BIN_DIR, "kid_data.bin")
|
||
with open(binpath, "wb") as b:
|
||
for (img, dx, dy, fl, sw) in frames:
|
||
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
|
||
b.write(bytes(seqtbl))
|
||
for (img, dx, dy, fl, sw) in gframes:
|
||
b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
|
||
print(f"kid_data.bin: {len(frames)*5 + len(seqtbl) + len(gframes)*5} Б (кадры Kid + seqtbl + кадры стража) -> {binpath}")
|
||
|
||
print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, "
|
||
f"{len(offs)} seq-оффсетов -> {OUT}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|