Alle Formatseiten ↓
Grafik und Medien

TAF / TSF – Truecolor-Animationen von TA: Kingdoms

Aus coreprime/kbot @ 0ab97bf · Auf GitHub bearbeiten

TAF speichert Sprite-Animationen in Truecolor für Total Annihilation: Kingdoms: Zaubereffekte, Explosionen, Feuerbälle, magische Ringe, Wasser-Sprites und die animierten Menühintergründe. Es verwendet den Containeraufbau von GAF aus Total Annihilation wieder, tauscht aber die palettenindizierten 8-Bit-Pixel gegen 16-Bit-ARGB; ein TAF bringt also seine eigenen Farben und einen echten Alphakanal mit – eine externe Palette ist nicht nötig. TSF ist die für Menschen lesbare Textform derselben Animation: ein durch geschweifte Klammern gegliedertes Skript, das der Oberflächenlader direkt liest.

Sequenz FireballA aus fireballa_1555.taf    einzelner BlueFire-Frame mit weichen Alpha-Kanten

Tip

Probier es selbst.

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

Alle Flags stehen in der kbot taf-/kbot tsf-Referenz der CLI.

Aus Go heraus. Verwende 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

Auf einen Blick

┌─ 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
└──────────────────────────────────────┘

Ein TAF ist eine einzige Animationssequenz – ein Array aus Frames, jeder ein Rechteck aus Breite × Höhe Truecolor-Pixeln zu 16 Bit in Little-Endian, mit einem Ursprungspunkt und einer Dauer in Ticks. Auf Binärebene gibt es weder Kompression noch Ebenen: Jeder Frame ist eine flache, unkomprimierte Bitmap.

Note

TAF ist der GAF-Container mit anderen Pixel-Nutzdaten. Wenn du GAF gelesen hast, kommen dir Header, Eintragstabelle, Sequenz-Header und Frame-Liste Byte für Byte bekannt vor. Nur die Auslegung von FrameInfo und die Pixelkodierung ändern sich. Beide existieren unter der Endung .taf nebeneinander: TA: Kingdoms liefert .taf-Dateien mit Palette aus (lies sie als GAF) und .taf-Dateien in Truecolor (diese Seite). kbot unterscheidet sie am Format-Byte jedes Frames – siehe Die beiden unterscheiden.


Header (12 Byte)

typedef struct {
    uint32 Version;        // 0x00010100 — identical to the GAF version word
    uint32 SequenceCount;  // Always 1 for a truecolor TAF
    uint32 Unknown1;       // Always 0
} TAFHeader;
Feld Anmerkungen
Version 0x00010100, dasselbe Wort wie in einem GAF von TA. Prüfe es, bevor du irgendeinem Offset vertraust.
SequenceCount Immer 1. Unmittelbar danach folgt ein absoluter uint32-Offset, der auf den SequenceHeader zeigt. Eine Datei mit mehreren Sequenzen ist kein Truecolor-TAF.
Unknown1 Immer null; beim Schreiben null ausgeben.

Sequenz (40 Byte)

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

Darauf folgen FrameCount × FrameListItem:

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

Warning

Das 32-Byte-Namensfeld kann hinter dem Terminator Datenmüll enthalten. Mehrere TAFs der Verkaufsversion lassen nach dem NUL nicht initialisierte Pufferbytes stehen – kbot bewahrt die genauen 32 Byte, damit ein Round-Trip aus Parsen → Serialisieren byte-identisch ist, aber nur der Text bis zum ersten NUL ist der echte Name.


Frame (24-Byte-Info + Nutzdaten)

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;

Hier weicht TAF von GAF ab. In einer FrameInfo von GAF ist Offset 0x09 das Flag Compressed, und die Offsets 0x0A–0x0B sind ein 16-Bit-LayerCount. Ein Truecolor-TAF verwendet dieselben Bytes mit folgender Bedeutung:

Offset Bedeutung in GAF Bedeutung in TAF
0x08 TransparencyIndex Transparency – unbenutzt, immer 0
0x09 Compressed (0/1) Format – 4 (ARGB4444) oder 5 (ARGB1555)
0x0A unteres Byte von LayerCount LayerCount – immer 0
0x0B oberes Byte von LayerCount Flag – 0x00 oder 0xFF, Zweck unbekannt

OriginX / OriginY sind der Ankerpunkt, an dem der Frame geblittet wird – meist die Mitte des Effekts, eine Explosion von 50×48 hat also den Ursprung 25,24. kbot übernimmt das Flag-Byte unverändert, damit Round-Trips byte-genau bleiben, auch wenn seine Bedeutung über den gesamten Asset-Bestand der Verkaufsversion hinweg nicht bestätigt ist.

Pixeldaten

PtrPixelData zeigt auf genau Width × Height uint16-Werte in Little-Endian – 2 Byte pro Pixel, von oben nach unten, von links nach rechts, ohne Kompression und ohne Zeilen-Header. Die 16 Bit werden je nach Format des Frames dekodiert:

ARGB4444 (Format == 4)

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

Jeder 4-Bit-Kanal wird durch Bit-Replikation (v<<4 | v) auf 8 Bit erweitert, also 0xF → 0xFF und 0x0 → 0x00. Die Erweiterung ist exakt umkehrbar, und genau das erlaubt kbot einen verlustfreien Round-Trip der Pixel.

ARGB1555 (Format == 5)

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

Das einzelne Alpha-Bit kennt nur alles oder nichts (0 → vollständig transparent, 1 → vollständig deckend); jeder 5-Bit-Farbkanal wird durch Replikation erweitert (v<<3 | v>>2). Nimm ARGB1555 für hart umrandete Sprites, die nur eine 1-Bit-Maske brauchen, und ARGB4444, wenn du 16 Stufen weiches Alpha brauchst (Rauch, Leuchten, Überblendungen).

Important

TAF braucht keine Palette. Anders als GAF – das nur Indizes speichert und auf palettes/palette.pal oder eine PCX-Begleitdatei von TA:K angewiesen ist – enthält jedes TAF-Pixel bereits seine eigene Farbe und sein Alpha. Das Rendern ist eine direkte Erweiterung von 16 auf 32 Bit, ganz ohne Nachschlagetabelle.


TAF mit Palette und TAF in Truecolor unterscheiden

Beide Arten von .taf teilen sich den GAF-Header und das Versionswort, anhand der ersten 12 Byte kannst du sie also nicht unterscheiden. Das Unterscheidungsmerkmal ist das Byte Format/Compressed des Frames bei Offset 0x09 der FrameInfo:

Byte bei 0x09 Die Datei ist Lesen mit
0 oder 1 Animation im GAF-Stil mit Palette (ein Wert von 0/1 bedeutet unkomprimiert/komprimiert) GAF-Lader + eine Palette
4 oder 5 Truecolor-Animation (ARGB4444 / ARGB1555) diese Seite (formats/tsf)

kbot taf prüft das Byte und weist eine Datei mit Palette mit einer klaren Fehlermeldung ab; nimm in diesem Fall kbot gaf. Die meisten Truecolor-TAFs von TA:K folgen außerdem einer Namenskonvention – einem Suffix _4444 oder _1555 (manabomb_1555.taf, bluefire_4444.taf) –, maßgeblich ist aber das Format-Byte, nicht der Name.


TSF – die Textform

TSF (TAF Source Format) ist dieselbe Animation, ausgedrückt als INI-ähnliches Skript, das durch geschweifte Klammern gegliedert ist. Der Oberflächenlader von TA: Kingdoms liest TSF für Menühintergründe direkt, und es ist das Austauschformat, das kbot ausgibt, wenn du ein TAF dekompilierst: ein Abschnitt pro Animation, ein verschachteltes Frame pro Frame und ein Layer pro Frame, das auf eine externe Bilddatei verweist.

/* anims/titlescreen.tsf — abridged */
[JPGTest]
{
    Looping = 0;
    [Frame0]
    {
        Delay = 0;
        [Layer0]
        {
            AnchorX = 0;
            AnchorY = 0;
            Filename = TitleScreen.jpg;
        }
    }
    [Frame1] { /* … */ }
}
Token Entspricht Anmerkungen
[Name] { … } der Animationssequenz Eine pro Dokument; Name wird zum Sequenznamen im TAF.
Looping (nur Hinweis) Hinweis für das Authoring; das binäre TAF hat kein Feld für Schleifen.
[FrameN] einem Frame Delay ist die Dauer in Spiel-Ticks.
Format Frame.Format ARGB4444 (Standard, wenn weggelassen) oder ARGB1555.
[LayerN] dem Bild des Frames Filename verweist auf ein externes PNG/JPG; AnchorX/Y werden zum Ursprung des Frames.

Ein kompiliertes TAF reduziert die einzige Ebene jedes Frames auf eine Truecolor-Bitmap. kbot erwartet genau eine Ebene pro Frame (das Zusammensetzen mehrerer Ebenen ist eine Annehmlichkeit beim Erstellen von Oberflächen, die der Compiler beim Bauen auflöst) – kbot tsf lint markiert alles, was sich nicht sauber kompilieren lässt.

Tip

Ein TSF untersuchen, ohne die Bilder anzufassen.

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

lint prüft nur die Struktur – mit kbot taf compile durchläufst du die vollständige Pipeline zum Laden der Bilder.


Beispiel im Detail – manabomb_1555.taf

alle zehn Frames von manabomb in einem Raster angeordnet

$ 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

Zehn Frames zu 50×48, jeder in seiner Mitte verankert (25,24) und 2 Ticks lang angezeigt – ein Explosionsstern von ⅔ Sekunde. Die ganze Datei ist 48.376 Byte groß: 50 × 48 × 2 = 4,800 Pixelbytes pro Frame × 10, dazu Header, Sequenz-Header, Frame-Liste und zehn Info-Datensätze zu 24 Byte.


Abläufe in der CLI

Der Befehlsbaum kbot taf deckt Untersuchen, Rendern, Konvertieren und den Kompilierzyklus TAF↔TSF ab; kbot tsf deckt die Textform ab. Jeder Befehl löst seine Eingabe über den aktiven kbot-Kontext oder ein ausdrückliches --vfs-Einhängen auf, genau wie kbot gaf und kbot studio; bloße Dateinamen wie manabomb_1555.taf werden also in jedem eingehängten Archiv gesucht.

Untersuchen und rendern

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 erzeugt standardmäßig APNG, das den vollen Alphakanal behält; --format gif quantisiert für Vorschauen und Chat-Clients auf eine Tabelle mit 255 Farben plus einen transparenten Platz (eine 1-Bit-Maske).

Round-Trip Dekompilieren ↔ Kompilieren

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 schreibt ein TSF plus ein PNG pro Frame; compile liest sie wieder in ein binäres TAF ein. Für jedes TAF, das kbot geparst hat, ist der Round-Trip byte-identisch – kbot taf roundtrip und kbot taf diff prüfen das beide.

Aus gängigen Formaten importieren

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 reduziert ein animiertes GIF (unter Beachtung seiner Disposal-Modi und der Verzögerungen pro Frame) zu einem Truecolor-TAF; from-sheet zerlegt einen Sprite-Streifen als PNG/JPG mit festem Raster in Frames. Bei beiden kannst du das Pixelformat des Ziels wählen.

Prüfen

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

MCP-Werkzeuge

Dieselben Funktionen stellt der MCP-Server bereit, damit ein Agent Animationen direkt untersuchen und zeigen kann:

Werkzeug Zweck
taf_info Vollständiges JSON pro Frame: Name, Größen, Ursprünge, Formate, Dauern.
taf_list Einzeilige Zusammenfassung als JSON.
taf_render Einen Frame rendern und direkt als PNG zurückgeben (optional auch speichern).
taf_sheet Alle Frames auf einen Kontaktbogen rendern, direkt als PNG zurückgegeben.
taf_export Ein animiertes GIF oder APNG auf die Platte schreiben.
taf_lint Strukturdiagnosen als JSON.
tsf_info Ein TSF-Dokument zusammenfassen (Animation, Frames, Dateinamen der Ebenen).
tsf_lint Die Form eines TSF-Dokuments prüfen.

taf_render und taf_sheet liefern einen Bild-Inhaltsblock zurück, ein Modell kann einen Zaubereffekt also ohne zusätzlichen Round-Trip zum Lesen einer Datei ansehen. Pfade werden im konfigurierten Ordner game-data aufgelöst, genauso wie bei jedem anderen MCP-Werkzeug von kbot.


Stolperfallen

Warning

.taf ist mehrdeutig. Eine .taf kann ein GAF mit Palette oder eine Truecolor-Animation sein. Prüfe immer das Format-Byte des Frames (0x09), nicht die Endung oder den Dateinamen – siehe Die beiden unterscheiden.

  • Genau eine Sequenz. Ein Truecolor-TAF enthält immer eine einzige Animation; ein SequenceCount ungleich 1 heißt, dass du eine andere (wahrscheinlich palettierte) Datei vor dir hast.
  • Niemals Kompression. Jeder Frame ist eine flache 16-Bit-Bitmap von Width×Height. Es gibt keinen RLE-Pfad wie bei GAF – große Flammeneffekte (composite02flame) erreichen ~600 KB, eben weil nichts gepackt ist.
  • Das Namensfeld kann Datenmüll enthalten. Die Bytes nach dem abschließenden NUL sind in mehreren Dateien der Verkaufsversion nicht initialisiert; bewahre für einen exakten Round-Trip alle 32 Byte, lies aber nur bis zum NUL.
  • Das Flag-Byte 0x0B ist ungeklärt. Beobachtet als 0x00 oder 0xFF. kbot übernimmt es unverändert; normalisiere es nicht, sonst stimmen Round-Trips nicht mehr überein.
  • Der GIF-Export ist verlustbehaftet. GIF kann nur eine 1-Bit-Maske und 255 Farben halten – weiches ARGB4444-Alpha wird zu harten Kanten. Nimm APNG, um den echten Alphakanal zu erhalten.
  • Ein Delay / eine Duration von 0 ist zulässig. Wie bei GAF ist ein Frame mit null Ticks ereignisgesteuert (das Menübeispiel JPGTest verwendet Delay = 0 für einen statischen Hintergrund). „Repariere“ das nicht auf einen Standardwert.

Typische Größen

Asset Beobachteter Bereich in TAFs von TA:K
Kleiner Effekt (bluefire_4444, 12 Frames zu etwa 32) ~30–60 KB
Explosion (manabomb_1555, 10 × 50×48) ~48 KB
Feuerball / Zauber (fireballa_1555) ~50–120 KB
Große zusammengesetzte Flamme (composite02flame) ~600 KB (unkomprimiertes Truecolor summiert sich)
Pixeldaten pro Frame genau Width × Height × 2 Byte

Siehe auch

  • GAF – der Container mit Palette, auf dem TAF aufbaut; lies ihn zuerst und verwende ihn für .taf-Dateien mit Palette.
  • PCX – wie Assets von TA:K mit Palette ihre Farben bekommen (Truecolor-TAF braucht keine).
  • Oberfläche von TA:K – das Menüsystem, dessen Lader TSF für animierte Hintergründe direkt verarbeitet.
  • TA und TA:K im Vergleich – was die beiden Spiele teilen und wo sie auseinandergehen.
  • Glossar – origin, tick, sub-frame.