| name | kbot |
| description | Toolkit for reverse-engineering and working with Total Annihilation game assets — COB/BOS unit scripts, HPI/UFO/CCX archives, GAF sprite animations, PCX images, Smacker (.smk/.zrb) video, PCX palettes, TDF/FBI/OTA configs, TNT maps, SCT sections, 3DO models, ALP/LHT/SHD lookup tables, and bitmap fonts. Use when the user mentions Total Annihilation, TA, Cavedog, Boneyards, or any of the listed file formats; when working with files under directories named like "totala", "ta-flattened", or with extensions .cob, .bos, .hpi, .ufo, .ccx, .gaf, .tnt, .sct, .3do, .fbi, .ota, .tdf, .smk, .zrb; or when asked to decompile/lint unit scripts, inspect or pack TA archives, or convert TA images and videos. |
| user-invocable | false |
kbot — Total Annihilation Toolkit Skill
kbot is a Go CLI that parses, inspects, transforms, and produces Total Annihilation (1997, Cavedog) game files. It is the canonical tooling for the Core Prime / Boneyards re-implementation project. There is one binary, kbot, with several format-specific subcommands plus an MCP server mode for AI assistants.
When to use this skill
Trigger when the conversation involves:
- Unit scripting:
.cob (compiled bytecode), .bos (source), .coba (assembly listing). Symptoms: animation hooks (Create, MotionControl, QueryPrimary), opcodes like move/turn/spin/sleep/emit-sfx, references to "piece", "static var", "signal mask".
- Archive packaging:
.hpi (HAPI/Cavedog), .ufo (mod archives, same format), .ccx (commander campaign).
- Graphics:
.gaf (multi-sequence sprite frames with palette indices + alpha), .pcx (paletted images, units' wireframes, GUI), TA's fixed 256-entry palette.
- Maps:
.tnt (terrain), .sct (reusable section). Mention of "start positions", "feature overlays", "tile grid", "height maps".
- Models:
.3do (object hierarchy with texture lists).
- Config:
.tdf/.fbi/.ota (text data files — units, weapons, missions, side data).
- Video/audio:
.smk / .zrb (Smacker movies), .wav, .mp3.
- Misc: bitmap fonts (1bpp MSB-first),
.alp/.lht/.shd color lookup tables, AI opponent profiles.
If the user mentions Total Annihilation, "TA", Cavedog, Boneyards, Core Prime, or asks to inspect/convert/build any of the above, kbot is the right tool — prefer it over hand-written parsers.
Two ways to invoke
1. CLI (default)
kbot <subcommand> [args]
./bin/kbot <subcommand> [args]
All read commands accept --stream to read from stdin and --target <path> to write somewhere other than stdout.
2. MCP server (for AI agents like Claude Desktop / Cursor)
kbot mcp --mount ~/games/totala
kbot mcp --http 127.0.0.1:8765 --mount ~/...
--mount <dir> is an allow-list; any path argument passed to a tool must resolve under one of the mounted roots. Multiple --mount flags are allowed. With no mounts the server runs permissively (every absolute path is accepted) — fine for local dev, unsafe for shared hosts. Never write to stdout when serving stdio: status logs go to stderr.
CLI subcommand reference
kbot cob — COB/BOS scripting
COB is compiled bytecode for unit AI/animation; BOS is the source language (Switech-era C-like). kbot is round-trip-perfect: decompile → compile and disassemble → assemble both produce byte-identical output.
| Sub-sub | Purpose |
|---|
decompile <file.cob> | COB → BOS source |
compile <file.bos> | BOS → COB |
disassemble <file.cob> | COB → assembly listing. -a for annotated (flow arrows + hex opcodes); -s <script> for a single function. |
assemble <file.coba> | assembly → COB |
lint <file-or-dir> | static analysis (see rules below). -q for summary only. |
roundtrip <dir> | validates byte-perfect round-trip across a tree. --detailed to enumerate failures. |
Lint rules (17 total; severities: ❌ error / ⚠️ warning / ℹ️ info):
unused-piece, unused-static, unused-local, always-true (if(1), while(1)), dead-code (if(0), while(0)), long-function (>100 instr), high-complexity (cyclomatic >15), invalid-call (❌ — call-script to missing function), speed-zero (move/turn with speed <0> — never completes), empty-function, duplicate-animation, sleep-only-guard, duplicate-if, raw-signal, unnamed-global (global_N), signal-never-signalled, recursive-call.
kbot hpi — HPI/UFO/CCX archives
kbot hpi list <archive> [-v] [-p "*.cob"]
kbot hpi info <archive>
kbot hpi extract <archive> [-t outdir] [-p "sounds/*"]
kbot hpi pack <directory> --target out.hpi
All read commands accept --stream.
kbot gaf — sprite animations
kbot gaf list <file.gaf>
kbot gaf export <file.gaf> --format gif|png [--sequence N]
kbot gaf dump <file.gaf> --target ./out [--format png]
kbot gaf build <dump-dir> --target rebuilt.gaf
png exports as APNG. frames.csv carries per-frame durations used by build.
kbot pcx — PCX images
kbot pcx describe <file.pcx>
kbot pcx info <file.pcx>
kbot pcx convert <file.pcx> --format png|gif|bmp [--target out.png]
kbot fnt — bitmap fonts
kbot fnt info <file.fnt>
kbot fnt describe <file.fnt> [--list]
kbot fnt render <file.fnt> --text "Hello" [--target hello.png] [--fg
kbot fnt sheet <file.fnt> [--target sheet.png]
kbot fnt dump <file.fnt> --target ./glyphs
kbot sct — SCT map sections
kbot sct info <file.sct>
kbot sct describe <file.sct>
kbot sct image <file.sct> [--target out.png]
kbot sct heightmap <file.sct> [--target h.png]
kbot sct minimap <file.sct> [--target mini.png]
kbot pal — palettes and color-lookup tables
kbot pal info <file.pal>
kbot pal describe <file.pal>
kbot pal swatch <file.pal> [--target pal.png] [--cell 16]
kbot pal convert <file.pal> --target out.gpl
kbot pal lookup <file.alp|.lht|.shd> [--palette ref.pal] [--target lut.png]
kbot tnt — TNT maps
Terrain layer of TA maps (header + tile grid + per-cell elevation/features + tile gfx + minimap). Sister .ota/metal-PCX files are not part of the TNT.
kbot tnt describe <file.tnt>
kbot tnt unpack <file.tnt> --target ./dir
kbot tnt unpack <file.tnt> --target ./dir --lossless
kbot tnt pack ./dir --target out.tnt
kbot tnt image <file.tnt> --target map.png
kbot tnt preview <file.tnt> --vfs ~/ta-flattened --target preview.png
kbot tnt heightmap <file.tnt> --target height.png
kbot tnt minimap <file.tnt> --target mini.png
kbot tnt ascii <file.tnt> --cols 64
unpack is lossy by default: only the unique feature names referenced in features.csv survive, and pack rebuilds the feature table from them. Pass --lossless to record the original feature table (plus any trailing scratch bytes Cavedog's tooling left in feature-name buffers as feature_raw_b64) in metadata.json; the resulting directory packs back to a byte-identical TNT across the full Cavedog corpus, including 0xFFFE/0xFFFC feature sentinels and inter-block scratch padding.
preview reuses the same tile render as image and overlays each placed feature's sprite (resolved via features/*.tdf -> anims/*.gaf in the VFS) plus a numbered circle at every Schema 0 StartPos in the sister .ota. Without --vfs it is equivalent to image.
kbot zrb — Smacker video
kbot zrb info <file.smk>
kbot zrb to-mp4 <file.smk> --target out.mp4
kbot zrb from-mp4 <in.mp4> --target out.smk
Requires FFmpeg on PATH for conversions.
kbot mount — virtual filesystem / explorer
kbot mount <root>
kbot mount <root> --server [--port 8080]
kbot mount <root> flatten --target <out>
The mount system layers HPI/UFO archives over a directory the same way the game's engine does (later archives override earlier ones). Use flatten to materialise the merged view onto disk — this is the canonical "give me an unpacked TA tree" command.
Interactive TTY commands: ls, cd, pwd, cat, describe, archives, stats, exit.
Web UI handles previews for COB, GAF, PCX, WAV/MP3, SMK/ZRB, TNT, SCT, 3DO, TDF/FBI/OTA, palettes (ALP/LHT/SHD), bitmap fonts, plus call-graph visualisation for scripts/signals.
kbot mcp — MCP server
See the Two ways to invoke section. The MCP server exposes a curated subset of the CLI as JSON-schema'd tools (see next section).
MCP tool reference
Exposed when running kbot mcp. All path and output arguments are validated against the configured --mount allow-list.
| Tool | Required args | Optional args | Returns |
|---|
cob_decompile | path | — | BOS source text |
cob_disassemble | path | script (single function name), annotated (bool, default false) | assembly listing text |
cob_lint | path (file or dir) | — | JSON {diagnostics: [{file, rule, severity, script, line, message}], summary: {rule: count}, files_linted, has_errors} |
cob_info | path | — | JSON {path, version, num_scripts, num_pieces, num_statics, script_names, piece_names, code_bytes} — fast, no decompile |
hpi_list | path | pattern (glob, e.g. '*.fbi') | file listing |
hpi_info | path | — | header + content summary (version, file count, compression ratio) |
hpi_extract_file | path, entry (in-archive path), output (on-disk dest) | — | bytes written + resolved output path |
gaf_list | path | — | sequences with name, frame count, total duration |
gaf_export | path, output | sequence (index, default 0), format (gif default, png = APNG) | path to rendered image |
pcx_describe | path | — | version, encoding, dimensions, bit depth, plane count, DPI, colour-type |
pcx_convert | path, output | format (png/gif/bmp; inferred from extension when omitted) | path to converted image |
tdf_parse | path | — | structured JSON tree preserving section name case and field order |
tnt_describe | path | — | JSON header summary, tile/feature counts, elevation stats, top features |
tnt_image | path, output | — | RGBA PNG render of the tile grid |
tnt_preview | path, output | — | RGBA PNG with feature sprites + numbered StartPos markers (needs a game-data folder; falls back to bare tile render otherwise). JSON result includes sprites_painted, sprites_missing, start_positions, sister_ota_found, overlay_applied. |
tnt_heightmap | path, output | normalize (bool, default false) | grayscale elevation PNG |
tnt_minimap | path, output | paletted (bool, default false) | minimap PNG (RGBA or 8-bit indexed) |
tnt_ascii | path | cols (number, default 64) | ASCII-art height map |
tnt_optimize | path, output | similarity (% of 255, default 1.0; 0 disables), keep_unused (bool, default false) | Writes a TNT with exact-duplicate, visually-similar (same heightmap footprint), and unused tile graphics consolidated. JSON result reports tiles_before/after, exact_merges, similarity_merges, unused_removed, tile_bytes_saved, output_file_size. |
fnt_describe | path | — | JSON {height, flags, glyph_count, min_width, max_width, mean_width, ranges} |
fnt_render | path, output, text | fg, bg (#rrggbb / #rrggbbaa / 'transparent') | rendered text PNG |
fnt_sheet | path, output | fg, bg | 16-column glyph sprite-sheet PNG |
sct_describe | path | — | JSON header + tile/attr counts, height stats, minimap presence |
sct_image | path, output | — | RGBA tile-grid PNG (32 px/tile) |
sct_heightmap | path, output | — | normalised grayscale elevation PNG |
sct_minimap | path, output | — | 128x128 embedded minimap PNG |
pal_describe | path | list_entries (bool, default false) | JSON unique/duplicate counts, TA-style flag, optional 256-entry list |
pal_swatch | path, output | cell (number, default 16) | 16x16 PNG swatch grid |
pal_convert | path, output | format (jasc/gpl/pal), name | converted palette file |
pal_lookup | path, output | palette (override .pal), cell (default 4) | 256x4 PNG render of an .ALP/.LHT/.SHD lookup table |
For everything else (3DO, ZRB, mount/flatten), use the CLI.
Source layout (when reading/extending the code)
kbot/
├── cmd/kbot/ CLI entry points (one *.go per subcommand)
├── formats/ Public format packages — import these directly from Go code
│ ├── ai/ AI opponent profiles
│ ├── fnt/ Bitmap fonts (1bpp, MSB-first)
│ ├── gaf/ Sprite animations + writer
│ ├── hpi/ HPI/UFO/CCX archives
│ ├── pal/ TA .PAL palettes + .ALP/.LHT/.SHD lookup tables
│ ├── pcx/ PCX images
│ ├── scripting/ COB/BOS bytecode
│ │ ├── assembly/ assembler + disassembler
│ │ ├── compiler/ BOS → COB
│ │ ├── decompiler/ COB → BOS
│ │ ├── linter/ 17-rule static analysis
│ │ └── parser/ lexer, parser, preprocessor
│ ├── sct/ map sections
│ ├── smacker/ Smacker video
│ ├── tdf/ text data files
│ ├── tdo/ 3DO models
│ └── tnt/ map terrain (reader, writer, unpack/pack, renderers)
├── filesystem/ virtual FS that layers archives over disk
└── internal/
├── assets/ embedded TA palette
├── cache/ on-disk file cache
├── explorer/ web UI server + React app
└── mcp/ MCP server + tool handlers
When adding format support: parsers and writers live under formats/<ext>/; CLI wiring goes in cmd/kbot/<ext>*.go; MCP exposure (if any) goes in internal/mcp/tools_<ext>.go and must be registered in internal/mcp/server.go.
Common workflows
Inspect a unit's behaviour
kbot cob info units/ARMCOM.cob
kbot cob decompile units/ARMCOM.cob
kbot cob lint units/ARMCOM.cob
Get a flat working copy of a TA install
kbot mount ~/games/total-annihilation --flatten --target ./ta-flattened
This is what TA_UNPACKED_PATH in .env.local should point at; tests rely on it.
Extract just the unit scripts from an archive
kbot hpi list totala1.hpi -p "*.cob"
kbot hpi extract totala1.hpi -p "units/*.cob" -t ./scripts
Convert TA artwork for external use
kbot pcx convert weapons.pcx --format png
kbot gaf export armcom.gaf --format png --sequence 0
Browse interactively
kbot mount ~/games/totala --server