Все страницы форматов ↓
Графика и медиа

TAF / TSF — полноцветные анимации TA: Kingdoms

Источник: coreprime/kbot @ 0ab97bf · Редактировать на GitHub

TAF хранит полноцветные анимации спрайтов для Total Annihilation: Kingdoms: эффекты заклинаний, взрывы, огненные шары, магические кольца, спрайты воды и анимированные фоны меню. Он использует структуру контейнера GAF из Total Annihilation, но вместо 8-битных пикселей с индексами палитры хранит 16-битный ARGB, так что TAF несёт собственный цвет и настоящий альфа-канал — внешняя палитра не нужна. TSF — читаемая текстовая форма той же анимации: скрипт с фигурными скобками, который загрузчик интерфейса читает напрямую.

последовательность FireballA из fireballa_1555.taf    один кадр BlueFire с мягкими краями альфа-канала

Tip

Попробуйте сами.

kbot taf list   anims/manabomb_1555.taf                                 # sequence + frame count
kbot taf export anims/fireballa_1555.taf --format gif -o fireball.gif    # animated preview
kbot taf sheet  anims/manabomb_1555.taf --cols 5 -o manabomb-sheet.png   # contact sheet
kbot taf decompile anims/manabomb_1555.taf --target ./manabomb           # → TSF + PNG layers

Все флаги описаны в справочнике по kbot taf / kbot tsf для командной строки.

Из Go. Используйте пакет formats/tsf:

import "github.com/coreprime/kbot/formats/tsf"

data, _ := os.ReadFile("anims/manabomb_1555.taf")
taf, _ := tsf.ParseTAF(data)
fmt.Println(taf.Name, len(taf.Frames))
img, _ := taf.FrameImage(0)   // *image.NRGBA, full alpha preserved

Кратко

┌─ Header (12B) ─┐
│ ver  1  pad    │   ver = 0x00010100, exactly one sequence
└────────┬───────┘
         ▼
┌─ Entry pointer ─┐  1 × uint32 absolute offset → SequenceHeader
└───┬─────────────┘
    ▼
┌─ SequenceHeader (40B) ─┐
│ frameCount, name[32]   │
└─────────┬──────────────┘
          ▼
┌─ FrameListItem (8B) × frameCount ─┐  ptr + tick duration
└───┬───────────────────────────────┘
    ▼
┌─ FrameInfo (24B) ─┐
│ size, origin,     │
│ format(4/5),      │   ← the GAF "compressed" byte, repurposed
│ layers=0, flag,   │
│ ptrPixelData      │
└─────┬─────────────┘
      ▼
┌─ Pixel data: Width×Height × uint16 ─┐  raw ARGB4444 or ARGB1555
└──────────────────────────────────────┘

TAF — это одна последовательность анимации, массив кадров, каждый из которых — прямоугольник ширина × высота из 16-битных полноцветных пикселей в little-endian с точкой привязки (origin) и длительностью в тиках. На двоичном уровне нет ни сжатия, ни слоёв: каждый кадр — плоский растр без сжатия.

Note

TAF — это контейнер GAF с другими пиксельными данными. Если вы читали про GAF, заголовок, таблица точек входа, заголовок последовательности и список кадров будут знакомы до байта. Меняются только толкование FrameInfo и кодирование пикселей. Оба варианта живут под расширением .taf: TA: Kingdoms поставляет и палитровые файлы .taf (читайте их как GAF), и полноцветные файлы .taf (эта страница). kbot различает их по байту формата в каждом кадре — см. Как их различить.


Заголовок (12 байт)

typedef struct {
    uint32 Version;        // 0x00010100 — identical to the GAF version word
    uint32 SequenceCount;  // Always 1 for a truecolor TAF
    uint32 Unknown1;       // Always 0
} TAFHeader;
Поле Примечания
Version 0x00010100, то же слово, что и в GAF из TA. Проверяйте его, прежде чем доверять любому смещению.
SequenceCount Всегда 1. Сразу за ним идёт одно абсолютное смещение uint32, указывающее на SequenceHeader. Файл с несколькими последовательностями — не полноцветный TAF.
Unknown1 Всегда ноль; при записи выводите ноль.

Последовательность (40 байт)

typedef struct {
    uint16 FrameCount;
    uint16 Unknown1;       // Always 1
    uint32 Unknown2;       // Always 0
    char   Name[32];       // NUL-padded, case-preserved
} TAFSequence;

Дальше следуют FrameCount × FrameListItem:

typedef struct {
    uint32 PtrFrameInfo;   // → FrameInfo
    uint32 Duration;       // Game ticks (30 ticks = 1 second)
} FrameListItem;

Warning

В 32-байтовом поле имени после терминатора может быть мусор. В нескольких оригинальных TAF после NUL остались неинициализированные байты буфера. kbot сохраняет все 32 байта в точности, чтобы round-trip «разбор → сериализация» был побайтно идентичным, но настоящее имя — это только текст до первого NUL.


Кадр (24 байта описания + полезная нагрузка)

typedef struct {
    uint16 Width;
    uint16 Height;
    int16  OriginX;        // Render anchor, may be negative
    int16  OriginY;
    uint8  Transparency;   // 0x08: always 0 (alpha lives in the pixels)
    uint8  Format;         // 0x09: 4 = ARGB4444, 5 = ARGB1555
    uint8  LayerCount;     // 0x0A: always 0 (no sub-frames)
    uint8  Flag;           // 0x0B: 0x00 or 0xFF, meaning unconfirmed
    uint32 Unknown2;       // 0x0C: always 0
    uint32 PtrPixelData;   // 0x10: → raw pixel bytes
    uint32 Unknown3;       // 0x14: always 0
} FrameInfo;

Именно здесь TAF расходится с GAF. В FrameInfo из GAF по смещению 0x09 находится флаг Compressed, а смещения 0x0A–0x0B — это 16-битный LayerCount. Полноцветный TAF использует те же байты иначе:

Смещение Значение в GAF Значение в TAF
0x08 TransparencyIndex Transparency — не используется, всегда 0
0x09 Compressed (0/1) Format — 4 (ARGB4444) или 5 (ARGB1555)
0x0A младший байт LayerCount LayerCount — всегда 0
0x0B старший байт LayerCount Flag — 0x00 или 0xFF, назначение неизвестно

OriginX / OriginY — точка привязки при выводе кадра; обычно это центр эффекта, так что у взрыва 50×48 точка привязки 25,24. kbot сохраняет байт Flag как есть ради побайтно точного round-trip, хотя его значение по всему набору оригинальных ресурсов не подтверждено.

Пиксельные данные

PtrPixelData указывает ровно на Width × Height значений uint16 в little-endian — 2 байта на пиксель, сверху вниз, слева направо, без сжатия и без заголовков строк. 16 бит декодируются в зависимости от Format кадра:

ARGB4444 (Format == 4)

 bit 15 ──────────────────────────── bit 0
┌─────┬─────┬─────┬─────┐
│  A  │  R  │  G  │  B  │   4 bits each
└─────┴─────┴─────┴─────┘
  15-12  11-8   7-4   3-0

Каждый 4-битный канал расширяется до 8 бит повторением битов (v<<4 | v), так что 0xF → 0xFF, а 0x0 → 0x00. Это расширение точно обратимо, и именно поэтому kbot может выполнять round-trip пикселей без потерь.

ARGB1555 (Format == 5)

 bit 15 ──────────────────────────── bit 0
┌─┬───────┬───────┬───────┐
│A│   R   │   G   │   B   │   1 + 5 + 5 + 5
└─┴───────┴───────┴───────┘
 15  14-10   9-5    4-0

Единственный бит альфы работает по принципу «всё или ничего» (0 — полностью прозрачный, 1 — полностью непрозрачный); каждый 5-битный цветовой канал расширяется повторением (v<<3 | v>>2). Используйте ARGB1555 для спрайтов с резкими краями, которым достаточно однобитной маски, и ARGB4444, когда нужны 16 уровней мягкой альфы (дым, свечение, затухание).

Important

TAF не нужна палитра. В отличие от GAF, который хранит только индексы и зависит от palettes/palette.pal или сопутствующего PCX в TA:K, каждый пиксель TAF уже содержит свой цвет и альфу. Отрисовка — прямое расширение 16 бит → 32 бита без всякой таблицы поиска.


Как отличить палитровый TAF от полноцветного

У обоих видов .taf общий заголовок и слово версии GAF, поэтому по первым 12 байтам их не различить. Различает их байт Format/Compressed кадра по смещению 0x09 в FrameInfo:

Байт по смещению 0x09 Файл — это Чем читать
0 или 1 Палитровая анимация в стиле GAF (значение 0/1 означает без сжатия / со сжатием) Загрузчик GAF + палитра
4 или 5 Полноцветная анимация (ARGB4444 / ARGB1555) эта страница (formats/tsf)

kbot taf проверяет этот байт и отказывается открывать палитровый файл, выдавая понятную ошибку; в этом случае берите kbot gaf. Большинство полноцветных TAF в TA:K также следуют соглашению об именах — суффикс _4444 или _1555 (manabomb_1555.taf, bluefire_4444.taf), — но решает байт формата, а не имя.


TSF — текстовая форма

TSF (TAF Source Format) — та же анимация, записанная в виде скрипта в духе INI с фигурными скобками. Загрузчик интерфейса TA: Kingdoms читает TSF напрямую для фонов меню, и именно в этом формате обмена kbot выдаёт результат декомпиляции TAF: одна секция на анимацию, вложенный Frame на каждый кадр и один Layer на кадр со ссылкой на внешний файл изображения.

/* anims/titlescreen.tsf — abridged */
[JPGTest]
{
    Looping = 0;
    [Frame0]
    {
        Delay = 0;
        [Layer0]
        {
            AnchorX = 0;
            AnchorY = 0;
            Filename = TitleScreen.jpg;
        }
    }
    [Frame1] { /* … */ }
}
Токен Соответствует Примечания
[Name] { … } Последовательности анимации Одна на документ; Name становится именем последовательности TAF.
Looping (справочно) Подсказка для автора; в двоичном TAF поля зацикливания нет.
[FrameN] Frame Delay — длительность в игровых тиках.
Format Frame.Format ARGB4444 (по умолчанию, если не указан) или ARGB1555.
[LayerN] Изображению кадра Filename ссылается на внешний PNG/JPG; AnchorX/Y становятся точкой привязки кадра.

Скомпилированный TAF сводит единственный слой каждого кадра в один полноцветный растр. kbot ожидает ровно один слой на кадр (наложение нескольких слоёв — удобство при создании интерфейса, которое компилятор разрешает во время сборки); kbot tsf lint отмечает всё, что не скомпилируется чисто.

Tip

Просмотр TSF, не трогая изображения.

kbot tsf info anims/titlescreen.tsf   # animation, frames, layer filenames
kbot tsf lint anims/titlescreen.tsf   # shape check; non-zero exit on errors

lint проверяет только структуру — чтобы прогнать весь конвейер загрузки изображений, используйте kbot taf compile.


Разбор примера — manabomb_1555.taf

все десять кадров manabomb, разложенные сеткой

$ kbot taf info anims/manabomb_1555.taf
TAF: anims/manabomb_1555.taf
Sequence:  "manabomb"
Frames:    10
Duration:  20 ticks (0.67s)

#  Size   Origin  Format    Duration  Flag
─  ────   ──────  ──────    ────────  ────
0  50x48  25,24   ARGB1555  2         0x00
1  50x48  25,24   ARGB1555  2         0x00
...
9  50x48  25,24   ARGB1555  2         0x00

Десять кадров 50×48, каждый привязан к своему центру (25,24) и показывается 2 тика — звезда взрыва длиной ⅔ секунды. Весь файл занимает 48 376 байт: 50 × 48 × 2 = 4,800 байт пикселей на кадр × 10, плюс заголовок, заголовок последовательности, список кадров и десять 24-байтовых записей описания кадров.


Работа в командной строке

Дерево команд kbot taf охватывает просмотр, отрисовку, преобразование и цикл компиляции TAF↔TSF; kbot tsf работает с текстовой формой. Каждая команда находит входной файл через активный контекст kbot или явно смонтированный --vfs, точно так же, как kbot gaf и kbot studio, поэтому голые имена файлов вроде manabomb_1555.taf ищутся во всех смонтированных архивах.

Просмотр и отрисовка

kbot taf info   anims/bluefire_4444.taf            # per-frame table
kbot taf list   anims/bluefire_4444.taf            # one-line summary
kbot taf render anims/bluefire_4444.taf --frame 4 -o frame4.png
kbot taf sheet  anims/bluefire_4444.taf --cols 6 --bg "#202830" -o sheet.png
kbot taf export anims/bluefire_4444.taf --format apng -o bluefire.png   # or --format gif

По умолчанию export выдаёт APNG, который сохраняет полный альфа-канал; --format gif квантует цвета до таблицы из 255 цветов плюс один прозрачный слот (однобитная маска) — для превью и чат-клиентов.

Round-trip: декомпиляция ↔ компиляция

kbot taf decompile anims/manabomb_1555.taf --target ./manabomb
#   → manabomb/manabomb.tsf  +  manabomb_0.png … manabomb_9.png

kbot taf compile ./manabomb/manabomb.tsf --images ./manabomb -o rebuilt.taf
kbot taf diff anims/manabomb_1555.taf rebuilt.taf      # exits 0 — identical

decompile записывает TSF и по одному PNG на кадр; compile читает их обратно в двоичный TAF. Для любого TAF, который kbot разобрал, round-trip побайтно идентичен — это проверяют и kbot taf roundtrip, и kbot taf diff.

Импорт из распространённых форматов

kbot taf from-gif   boom.gif   --format argb4444 --name Boom        -o boom.taf
kbot taf from-sheet strip.png  --frame-width 64 --frame-height 64 \
                               --count 8 --delay 3 --format argb1555 -o strip.taf

from-gif сводит анимированный GIF (с учётом его режимов смены кадров и задержек каждого кадра) в полноцветный TAF; from-sheet нарезает на кадры ленту спрайтов PNG/JPG с фиксированной сеткой. Обе команды позволяют выбрать целевой формат пикселей.

Проверка

kbot taf lint anims/manabomb_1555.taf   # structural findings; non-zero exit on errors

Инструменты MCP

Те же возможности доступны через MCP-сервер, так что агент может изучать анимации и показывать их напрямую:

Инструмент Назначение
taf_info Полный JSON по кадрам: имя, размеры, точки привязки, форматы, длительности.
taf_list Краткая сводка в JSON.
taf_render Отрисовать один кадр и вернуть его прямо в ответе как PNG (по желанию также сохранить).
taf_sheet Отрисовать все кадры на лист кадров (contact sheet) и вернуть его прямо в ответе как PNG.
taf_export Записать на диск анимированный GIF или APNG.
taf_lint Диагностика структуры в JSON.
tsf_info Сводка по документу TSF (анимация, кадры, имена файлов слоёв).
tsf_lint Проверка структуры документа TSF.

taf_render и taf_sheet возвращают блок с изображением, поэтому модель может посмотреть эффект заклинания без отдельного чтения файла. Пути разрешаются относительно настроенной папки game-data так же, как в любом другом MCP-инструменте kbot.


Подводные камни

Warning

У .taf два значения. .taf может быть палитровым GAF или полноцветной анимацией. Всегда проверяйте байт формата кадра (0x09), а не расширение или имя файла — см. Как их различить.

  • Ровно одна последовательность. Полноцветный TAF всегда содержит одну анимацию; SequenceCount, отличный от 1, означает, что перед вами другой (скорее всего, палитровый) файл.
  • Никакого сжатия. Каждый кадр — плоский 16-битный растр Width×Height. Пути через RLE, как в GAF, нет — большие эффекты пламени (composite02flame) доходят до ~600 КБ именно потому, что ничего не упаковано.
  • В поле имени может быть мусор. Байты после завершающего NUL в нескольких оригинальных файлах не инициализированы; для точного round-trip сохраняйте все 32 байта, но читайте только до NUL.
  • Назначение байта флага 0x0B не выяснено. Встречаются 0x00 и 0xFF. kbot сохраняет его как есть; не нормализуйте его, иначе round-trip перестанет совпадать.
  • Экспорт в GIF идёт с потерями. GIF вмещает только однобитную маску и 255 цветов — мягкая альфа ARGB4444 превращается в резкие края. Чтобы сохранить настоящий альфа-канал, используйте APNG.
  • Delay / Duration, равные 0, допустимы. Как и в GAF, кадр с нулём тиков управляется событиями (в примере меню JPGTest для статичного фона используется Delay = 0). Не «исправляйте» это на значение по умолчанию.

Типичные размеры

Ресурс Диапазон в TAF из TA:K
Небольшой эффект (bluefire_4444, 12 кадров размером около 32) ~30–60 КБ
Взрыв (manabomb_1555, 10 × 50×48) ~48 КБ
Огненный шар / заклинание (fireballa_1555) ~50–120 КБ
Большое составное пламя (composite02flame) ~600 КБ (полноцветные данные без сжатия быстро набирают объём)
Пиксельные данные кадра Ровно Width × Height × 2 байт

См. также

  • GAF — палитровый контейнер, на котором построен TAF; прочитайте его первым и используйте для палитровых файлов .taf.
  • PCX — откуда палитровые ресурсы TA:K берут цвета (полноцветному TAF они не нужны).
  • Интерфейс TA:K — система меню, загрузчик которой читает TSF напрямую для анимированных фонов.
  • Форматы TA и TA:K — что у двух игр общего и в чём они расходятся.
  • Глоссарий — origin, tick, sub-frame.