所有格式页面 ↓
图形与媒体

TAF / TSF——横扫千军:王国的真彩色动画

来自 coreprime/kbot @ 0ab97bf · 在 GitHub 上编辑

TAF 存储《横扫千军:王国》的真彩色精灵图动画:法术特效、爆炸、火球、魔法光环、水面精灵图以及动态菜单背景。它沿用《横扫千军》的 GAF 容器布局,但把 8 位调色板索引像素换成了 16 位 ARGB,因此 TAF 自带颜色和真正的 alpha 通道——不需要外部调色板。TSF 是同一动画便于阅读的文本形式:一种以花括号分隔的脚本,界面加载器会直接读取它。

fireballa_1555.taf 中的 FireballA 序列    一帧带柔和 alpha 边缘的 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

所有参数见 CLI 的 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 位真彩色像素组成,带一个原点和一个以 tick 计的时长。在二进制层面上没有压缩,也没有分层:每一帧都是一张扁平的、未压缩的位图。

Note

TAF 就是换了一种像素载荷的 GAF 容器。 读过 GAF 的话,文件头、条目表、序列头和帧列表都会逐字节地眼熟。只有 FrameInfo 的解读方式和像素编码不同。两者共用 .taf 扩展名:横扫千军:王国发布的既有带调色板的 .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,与 TA 的 GAF 所带的字相同。在信任任何偏移量之前先校验它。
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 个字节,使解析 → 序列化的往返逐字节一致,但只有第一个 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 的分歧就在这里。在 GAF 的 FrameInfo 中,偏移量 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 是绘制(blit)该帧时所用的锚点——通常是特效的中心,所以一个 50×48 的爆炸的原点是 25,24。尽管 Flag 字节的含义在全部零售版资源中都未得到确认,kbot 仍原样保留它,以实现逐字节一致的往返。

像素数据

PtrPixelData 指向恰好 Width × Height 个小端序 uint16 值——每像素 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 位通道通过位复制(v<<4 | v)扩展为 8 位,因此 0xF → 0xFF,0x0 → 0x00。这种扩展是完全可逆的,kbot 正是借此无损地往返像素。

ARGB1555(Format == 5)

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

单个 alpha 位非此即彼(0 → 完全透明,1 → 完全不透明);每个 5 位颜色通道通过复制(v<<3 | v>>2)扩展。只需要 1 位镂空(cutout)的硬边精灵图用 ARGB1555;需要 16 级柔和 alpha(烟雾、光晕、渐隐)时用 ARGB4444。

Important

TAF 不需要调色板。 GAF 只存储索引,依赖 palettes/palette.pal 或 TA:K 的附属 PCX;而 TAF 的每个像素都已包含自己的颜色和 alpha。渲染就是直接把 16 位扩展为 32 位,不涉及任何查找表。


区分带调色板的 TAF 与真彩色 TAF

两种 .taf 共用 GAF 的文件头和版本字,所以无法凭前 12 个字节区分它们。判别依据是 FrameInfo 偏移量 0x09 处的帧 Format/Compressed 字节:

0x09 处的字节 文件是 读取方式
0 或 1 带调色板的 GAF 式动画(0/1 表示未压缩/已压缩) GAF 加载器 + 调色板
4 或 5 真彩色动画(ARGB4444 / ARGB1555) 本页(formats/tsf)

kbot taf 会校验这个字节,遇到带调色板的文件时给出明确的错误并拒绝处理;这种情况请改用 kbot gaf。大多数 TA:K 真彩色 TAF 也遵循一个文件名约定——带 _4444 或 _1555 后缀(manabomb_1555.taf、bluefire_4444.taf)——但起决定作用的是格式字节,而不是名称。


TSF——文本形式

TSF(TAF Source Format,TAF 源格式)是用花括号分隔、类似 INI 的脚本表达的同一个动画。横扫千军:王国的界面加载器会直接读取 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 是以游戏 tick 计的时长。
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 个 tick——一个持续 ⅔ 秒的爆炸星芒。整个文件 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,它保留完整的 alpha 通道;--format gif 会量化为一张 255 色的颜色表外加一个透明槽(1 位镂空),适合预览和聊天客户端。

反编译 ↔ 编译往返

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。对于 kbot 解析过的任何 TAF,往返都是逐字节一致的——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 返回一个图像内容块,所以模型无需再单独读一次文件就能预览法术特效。路径的解析方式与 kbot 的其他所有 MCP 工具相同,都相对于配置的 game-data 文件夹。


坑点

Warning

.taf 有多重含义。 一个 .taf 可能是带调色板的 GAF,也可能是真彩色动画。一定要检查帧格式字节(0x09),而不是看扩展名或文件名——见区分两者。

  • 恰好一个序列。 真彩色 TAF 总是只含一个动画;SequenceCount 不是 1 的话,你看到的就是另一种文件(很可能是带调色板的)。
  • 从不压缩。 每一帧都是一张扁平的 Width×Height 16 位位图,没有 GAF 那样的 RLE 路径——大型火焰特效(composite02flame)能达到约 600 KB,正是因为什么都没有压缩。
  • 名称字段可能含有垃圾数据。 在几个零售版文件中,结尾 NUL 之后的字节未经初始化;要实现精确的往返就保留全部 32 个字节,但读取时只读到 NUL 为止。
  • 0x0B 标志字节的含义不明。 观察到的值为 0x00 或 0xFF。kbot 原样保留它;不要把它规范化,否则往返结果就不再一致。
  • GIF 导出有损。 GIF 只能容纳 1 位镂空和 255 种颜色——ARGB4444 的柔和 alpha 会变成硬边。要保留真正的 alpha 通道请用 APNG。
  • Delay / Duration 为 0 是合法的。 与 GAF 一样,零 tick 的帧由事件驱动(菜单的 JPGTest 示例对静态背景使用 Delay = 0)。不要把它“修正”成某个默认值。

典型大小

资源 在 TA:K 的 TAF 中观察到的范围
小型特效(bluefire_4444,12 帧,每帧约 32 像素) 约 30–60 KB
爆炸(manabomb_1555,10 × 50×48) 约 48 KB
火球 / 法术(fireballa_1555) 约 50–120 KB
大型合成火焰(composite02flame) 约 600 KB(未压缩的真彩色积少成多)
每帧像素数据 恰好 Width × Height × 2 字节

另见

  • GAF——TAF 所基于的带调色板容器;先读它,带调色板的 .taf 文件也用它处理。
  • PCX——带调色板的 TA:K 资源如何获得颜色(真彩色 TAF 不需要)。
  • 横扫千军:王国界面——其加载器直接读取 TSF 作为动态背景的菜单系统。
  • TA 与 TA:K 格式对比——两款游戏共有什么、在哪里分道扬镳。
  • 术语表——origin、tick、sub-frame。