kbot

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: MIT

README

KBot - Total Annihilation Toolkit

KBot Logo

Go Reference GitHub issues GitHub commit activity

A toolkit for working with Total Annihilation game assets. KBot provides a unified CLI for managing the various proprietary file formats used by the game, along with a browser-based studio (asset explorer, map editor, unit viewer, and live sandbox). There are also development tools, allowing advanced debugging and processing of various formats, including the COB/BOS scripting language.

Installation

go install github.com/coreprime/kbot/cmd/kbot@latest

Or build from source:

git clone https://github.com/coreprime/kbot.git
cd kbot
task build        # builds the Studio web assets and kbot binary
task install      # installs kbot to $GOPATH/bin

Getting Started

To run the visual explorer for Total Annihilation:

  • Perform the installation steps
  • Launch kbot studio ~/games/totala and open the Files tab to browse, preview, and inspect every asset in the install

To develop/code against the project:

  • Create a flattened version of your Total Annihilation gmae assets with kbot mount flatten ~/games/totala ~/games/totala-flattened
  • Clone .env to .env.local and set your paths
  • task build / task lint will run the code quality checks.

Table of Contents

Commands

kbot ctx — Working-Directory Contexts

Register named Total Annihilation installs (packed or flattened) so the other kbot subcommands that rely on a virtual filesystem can pick them up automatically — no more typing --vfs ~/games/totala on every command.

Contexts persist to ~/.kbot (a small JSON file, conceptually equivalent to ~/.kube/config). One context is marked current at any time; the KBOT_CONTEXT environment variable overrides the persisted current for the running process so you can flip between installs per-shell without editing the file.

# Register a packed TA install and mark it current
kbot ctx add ~/games/totala --alias ta-gog --game totala --version 3.1c

# Register a TA: Kingdoms install alongside
kbot ctx add ~/games/tak --alias kingdoms --game takingdoms

# Register a flattened directory you keep for development
kbot ctx add ~/ta-flattened --alias ta-flat --game totala --version 3.1c-flat

# Adopt the current directory.  If it's already registered, just switch
# to it; otherwise launch a small interactive prompt for alias, game,
# and version.
cd ~/games/totala
kbot ctx here

# Show every registered context (the current one is starred).
# `kbot ctx` with no subcommand does the same; use `kbot ctx --help`
# for the full help text.
kbot ctx
kbot ctx list

# Print the active context's path — handy in shell substitution
cd "$(kbot ctx path)"
ls "$(kbot ctx path)/units"

# Print a specific context's path without switching to it
kbot ctx path --alias kingdoms

# Switch the persisted current context
kbot ctx use kingdoms

# Per-shell override (does not touch ~/.kbot)
KBOT_CONTEXT=ta-flat kbot tnt preview "metal heck.tnt" --target preview.png

# Replace an existing entry (e.g. you moved the game folder)
kbot ctx add ~/new/totala --alias ta-gog --game totala --version 3.1c --replace

# Remove a context
kbot ctx delete kingdoms

--game must be one of totala, takingdoms, or custom. custom is the escape hatch for directories that look like a TA layout but aren't an official release (mod test-beds, partial extractions, etc.).

Where contexts apply. Any subcommand that mounts a VFS will fall back to the active context when its path argument is omitted:

Command Without an arg/flag
kbot mount Mounts the active context.
kbot mount flatten --target … Flattens the active context.
kbot tnt preview <file.tnt> Uses the active context as the VFS root for sprites and the sister .ota.
kbot mcp Registers the active context as the default game-data folder.
kbot cob lint Lints the active context's tree when no path is passed.
kbot cob roundtrip Scans the active context's tree when no path is passed.
kbot gaf roundtrip Scans the active context's tree when no path is passed.

Explicit arguments (a positional path, --vfs, or --game-data) always override the context. Use KBOT_CONTEXT=<alias> to pick a different registered context just for one invocation.

The file format is plain JSON, so you can hand-edit it if you need to:

{
  "current": "ta-gog",
  "contexts": {
    "ta-gog":    { "path": "/Users/me/games/totala", "game": "totala",     "version": "3.1c" },
    "kingdoms":  { "path": "/Users/me/games/tak",    "game": "takingdoms" },
    "ta-flat":   { "path": "/Users/me/ta-flattened", "game": "totala",     "version": "3.1c-flat" }
  }
}

kbot cob — COB/BOS Scripting

Work with compiled unit scripts (COB bytecode) and their BOS source.

# Decompile COB to BOS source
kbot cob decompile unit.cob
kbot cob decompile unit.cob --target unit.bos

# Compile BOS source to COB
kbot cob compile unit.bos --target unit.cob

# Disassemble COB to assembly listing
kbot cob disassemble unit.cob
kbot cob disassemble unit.cob -a          # annotated with flow arrows
kbot cob disassemble unit.cob -s Create   # single script

# Assemble back to COB
kbot cob assemble unit.coba --target unit.cob

# Lint for common issues
kbot cob lint unit.cob
kbot cob lint scripts/                     # lint a whole directory
kbot cob lint scripts/ -q                  # summary only
kbot cob lint scripts/ --ci > cob.sarif    # SARIF 2.1.0 for CI ingest

# Roundtrip validation (byte-perfect decompile→compile and disassemble→assemble)
kbot cob roundtrip scripts/
kbot cob roundtrip scripts/ --detailed

All commands support --stream to read from stdin and --target to write to a file (default: stdout).

Lint rules:

Rule Severity Description
unused-piece ⚠️ warning Piece declared but never used
unused-static ⚠️ warning Global variable never accessed
unused-local ⚠️ warning Local variable allocated but unused
always-true ℹ️ info if(1) / while(1) — always-true condition
dead-code ⚠️ warning if(0) / while(0) — unreachable code
long-function ⚠️ warning Function exceeds 100 instructions
high-complexity ⚠️ warning Cyclomatic complexity > 15
invalid-call ❌ error call-script to non-existent function
speed-zero ⚠️ warning move/turn with speed <0> — never completes
empty-function ℹ️ info Function body is only return 0
duplicate-animation ⚠️ warning Identical sequential animation command
sleep-only-guard ℹ️ info if block contains only a sleep
duplicate-if ℹ️ info Back-to-back identical if conditions
raw-signal ℹ️ info Signal uses raw number (BOS only)
unnamed-global ℹ️ info Static var uses global_N naming (BOS only)
signal-never-signalled ⚠️ warning set-signal-mask watches a signal nobody sends
recursive-call ⚠️ warning call-script forms a cycle

kbot hpi — Archive Files

Manage HPI, UFO, and CCX archive files.

# List archive contents
kbot hpi list archive.hpi
kbot hpi list archive.hpi -v               # verbose (sizes, compression)
kbot hpi list archive.hpi -p "*.wav"       # filter by pattern

# Extract files
kbot hpi extract archive.hpi
kbot hpi extract archive.hpi -t ./output   # target directory
kbot hpi extract archive.hpi -p "sounds/*" # extract matching files

# Pack a directory into an archive
kbot hpi pack ./content --target archive.hpi

# Show archive details
kbot hpi info archive.hpi

All read commands support --stream to read the archive from stdin.


kbot gaf — Sprite Animations

Work with GAF animation files containing sprite sequences.

# List sequences
kbot gaf list sprites.gaf

# Export a sequence as GIF or PNG
kbot gaf export sprites.gaf --format gif
kbot gaf export sprites.gaf --format png --sequence 3

# Dump all sequences and frames to a folder
kbot gaf dump sprites.gaf --target ./sprites --format png

# Build a GAF from a dump folder
kbot gaf build ./sprites --target rebuilt.gaf

The dump output includes a frames.csv in each sequence folder with timing metadata. The build command reads this CSV to reconstruct frame durations.


kbot taf — Truecolor Animations

Work with TA: Kingdoms truecolor (16-bit ARGB) animation files. Unlike GAF, a TAF carries its own colour and alpha, so no palette is needed. See docs/formats/taf.md for the format.

# Inspect
kbot taf info anims/manabomb_1555.taf      # per-frame table
kbot taf list anims/manabomb_1555.taf      # one-line summary
kbot taf lint anims/manabomb_1555.taf      # structural validation

# Render
kbot taf render anims/bluefire_4444.taf --frame 4 -o frame4.png
kbot taf sheet  anims/manabomb_1555.taf --cols 5 --bg "#202830" -o sheet.png
kbot taf export anims/fireballa_1555.taf --format apng -o anim.png   # or --format gif

# Decompile / compile round-trip (TAF <-> TSF + PNG layers)
kbot taf decompile anims/manabomb_1555.taf --target ./manabomb
kbot taf compile   ./manabomb/manabomb.tsf --images ./manabomb -o rebuilt.taf
kbot taf diff anims/manabomb_1555.taf rebuilt.taf      # verify identical
kbot taf roundtrip anims/manabomb_1555.taf             # byte-for-byte check

# Import from common formats
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 -o strip.taf

export defaults to APNG (full alpha); --format gif produces a 1-bit-cutout preview. Every command resolves bare filenames through the active kbot context or an explicit --vfs mount.


kbot tsf — Animation Text Scripts

Inspect and validate TSF documents — the human-readable, brace-delimited text form of a TAF that the TA: Kingdoms GUI loader reads directly for menu backgrounds. kbot taf decompile emits TSF and kbot taf compile reads it back.

# Summarise the animation, frames and layer images
kbot tsf info anims/titlescreen.tsf

# Check the document matches the compiler's expectations
kbot tsf lint anims/titlescreen.tsf

lint checks document shape only (one animation, one Filename'd layer per frame, recognised pixel formats); use kbot taf compile to exercise the full image-loading pipeline.


kbot pcx — PCX Images

Inspect and convert PCX image files.

# Describe a PCX file (detailed metadata)
kbot pcx describe image.pcx

# Convert to PNG, GIF, or BMP
kbot pcx convert image.pcx --format png
kbot pcx convert image.pcx --format png --target output.png

# One-line info summary
kbot pcx info image.pcx

kbot fnt — Bitmap Fonts

Inspect and render Total Annihilation 1-bpp bitmap fonts. Each .fnt file holds up to 256 glyphs sharing a single height; widths vary per glyph.

# One-line summary
kbot fnt info comix.fnt

# Detailed metadata (height, flags, glyph count, code-point coverage)
kbot fnt describe comix.fnt
kbot fnt describe comix.fnt --list      # also enumerate every defined glyph

# Render a string to PNG
kbot fnt render comix.fnt --text "Hello TA" --target hello.png
kbot fnt render armfont.fnt --text "Commander" --fg "#ffff00" --bg transparent

# Render every defined glyph as a 16-column sprite sheet
kbot fnt sheet comix.fnt --target sheet.png

# Dump one PNG per glyph (named U+00XX.png) into a directory
kbot fnt dump comix.fnt --target ./glyphs

Colors accept #rrggbb, #rrggbbaa, or the literal transparent / none.


kbot sct — Map Sections

Inspect and render Total Annihilation .SCT map sections — the reusable tile blocks the official map editor stitches together to build .TNT maps.

# One-line summary
kbot sct info hill01.sct

# Detailed metadata (header, tile/attr counts, height stats)
kbot sct describe hill01.sct

# Render the full tile grid (32 px per tile) to PNG
kbot sct image hill01.sct --target hill01.png

# Export the elevation grid (16 px resolution) as a normalised grayscale PNG
kbot sct heightmap hill01.sct --target hill01-h.png

# Export the embedded 128x128 minimap as PNG
kbot sct minimap hill01.sct --target hill01-mini.png

kbot crt — TA: Kingdoms Scenarios

Inspect the .crt scenario sidecar that pairs with a TA: Kingdoms .tnt map. It holds the units placed before play, the per-player rule engine and named trigger regions. Multiplayer maps ship an empty stub; campaign and special maps populate every section. See docs/formats/takmap.md for the binary layout.

# Unit-type breakdown, rule counts and trigger regions
kbot crt describe "king of the hill.crt"

# Add every placement with its position, owner and facing
kbot crt describe --verbose "savannah hunt.crt"

kbot pal — Palettes & Lookup Tables

Inspect and convert Total Annihilation .PAL palettes, plus the related 1024-byte .ALP / .LHT / .SHD 256×4 color-index lookup tables used for shadow blending and light levels.

# One-line summary (size, unique colors, duplicates, TA-style flag)
kbot pal info palette.pal

# Every entry with hex + RGB
kbot pal describe palette.pal

# 16x16 PNG swatch grid (index 0 hatched with magenta to highlight the
# transparent sentinel)
kbot pal swatch palette.pal --target palette.png --cell 16

# Convert to editor-friendly formats
kbot pal convert palette.pal --target palette.gpl              # GIMP Palette
kbot pal convert palette.pal --target palette.txt --format jasc  # JASC-PAL text
kbot pal convert palette.pal --target re-emitted.pal --format pal  # binary TA .PAL

# Render an .ALP/.LHT/.SHD lookup table as a 256x4 PNG using the embedded
# palette (or pass --palette to use a specific one)
kbot pal lookup palette.alp --target alp.png
kbot pal lookup palette.lht --palette palette.pal --target lht.png

kbot tnt — TNT Maps

Inspect, render, unpack and pack Total Annihilation .TNT map terrain.

# Summary on the console
kbot tnt describe "metal heck.tnt"

# Render artefacts
kbot tnt image     "metal heck.tnt" --target map.png         # full RGBA map (1:1, 32 px / tile)
kbot tnt heightmap "metal heck.tnt" --target height.png      # 8-bit grayscale (16 px / cell)
kbot tnt buildmap  "metal heck.tnt" --target build.png       # per-cell buildability classification
kbot tnt voidmap   "metal heck.tnt" --target voids.png       # engine-void mask (Feature == 0xFFFC only)
kbot tnt minimap   "metal heck.tnt" --target mini.png        # embedded minimap

# Same as `image`, but composite feature sprites and draw numbered StartPos
# markers from the sister .ota.  --vfs points at a flattened TA install (or
# any directory containing features/*.tdf and anims/*.gaf).
kbot tnt preview "metal heck.tnt" --vfs ~/ta-flattened --target preview.png

# Pick a different schema's StartPos set (0-based; default 0).
kbot tnt preview "metal heck.tnt" --schema 2 --target preview-s2.png

# --vfs may be omitted when a kbot context is active (see `kbot ctx`)
kbot tnt preview "metal heck.tnt" --target preview.png

# Tiny ASCII rendering (dev-joke / quick sanity check)
kbot tnt ascii "metal heck.tnt" --cols 64

# Decompose into editable files
kbot tnt unpack "metal heck.tnt" --target ./metal_heck

# Preserve the original feature table verbatim so the unpack/pack round-trip
# is byte-identical (default unpack drops trailing scratch bytes Cavedog's
# tooling left in feature-name buffers; pack rebuilds the table from the
# unique names referenced in features.csv).
kbot tnt unpack "metal heck.tnt" --target ./metal_heck --lossless

# Reverse the unpack
kbot tnt pack ./metal_heck --target metal_heck.tnt

Unpack layout (--target <dir>):

<dir>/
  map.png            full RGBA render of the tile grid
  heightmap.png      8-bit grayscale, pixel = raw elevation byte
  minimap.png        paletted PNG of the embedded minimap
  tiles/<n>.png      paletted 32×32 PNG per unique tile
  tilemap.csv        2D grid of tile indices (rows = y, cols = x)
  features.csv       feature_index,name,attr_x,attr_y per placement
  metadata.json      header constants + round-trip info (feature table when --lossless)

heightmap.png is unnormalised so the pixel value equals the raw elevation byte — round-trip safe. Pass --normalize to stretch the range for human viewing. minimap.png is paletted so palette indices survive the round-trip.

kbot tnt unpack is lossy by default: the feature name table is omitted from metadata.json and kbot tnt pack rebuilds it from the unique names in features.csv. This loses any trailing scratch bytes the original tooling left in the table. Pass --lossless to record the original feature table (and the raw feature bytes as feature_raw_b64) in metadata.json so the directory packs back to a byte-identical TNT.

Buildmap classification (kbot tnt buildmap):

The PNG is at attribute-cell resolution (one pixel per 16×16 cell, same as heightmap). Each cell is classified, in priority order:

Colour Meaning
Black engine-void — TileAttr.Feature == 0xFFFC
Red a feature is placed in the cell (rocks, trees, geo-vents, ...)
Blue underwater — Height is below sea level
Yellow cliff edge — |Δheight| to a 4-neighbour exceeds 32 game units
Green buildable

By default the .tnt header's SeaLevel field drives the underwater check; pass --sealevel <n> to override (0 disables the check). 0xFFFD / 0xFFFE are not classified as void — see docs/formats/tnt.md for the rationale (Metal Heck and a few other early Cavedog maps mark steam vents this way, and those cells are demonstrably buildable in-engine).

Voidmap (kbot tnt voidmap):

A transparent PNG at attribute-cell resolution. Cells with Feature == 0xFFFC are opaque red; everything else is transparent — overlay it on a kbot tnt image render to highlight just the engine-void areas.

The Studio's Advanced › Export menu offers the same artefacts plus the minimap and the active-schema preview:

Menu item Backend CLI / MCP equivalent
Export Minimap client canvas snapshot (n/a)
Export Full Render tnt.RenderTileMap + tntpreview.ComposeWith (active schema's StartPos markers) kbot tnt preview --schema <ActiveSchema> / tnt_preview
Export Map Image tnt.RenderTileMap (bare tile grid, no overlays) kbot tnt image / tnt_image
Export Buildmap tnt.RenderBuildMap kbot tnt buildmap / tnt_buildmap
Export Voidmap tnt.RenderVoidMap kbot tnt voidmap / tnt_voidmap

Linting a map:

kbot tnt lint runs the same checks Studio's Quality Checker dialog applies, plus the tile-pool diagnostics that kbot tnt optimize is built on. No files are modified — the command is read-only and returns exit-1 when any issue is reported (handy for CI).

By default the command mounts the active kbot context (kbot ctx) as a virtual filesystem, so the path argument can be a bare basename, a virtual path inside an HPI / GP3 archive, or an absolute disk path. The sibling .ota and the metal-proximity feature registry are picked up from the same VFS automatically.

# Bare basename — searched against maps/ in the active context VFS.
kbot tnt lint "metal heck.tnt"

# Virtual path — addresses a file inside an HPI archive directly.
kbot tnt lint "maps/the pass.tnt"

# Local file on disk (absolute or relative to cwd) still works.
kbot tnt lint ./my-edit.tnt

# Override the mount root for one invocation.
kbot tnt lint --vfs ~/ta-flattened "metal heck.tnt"

# Skip the quality pass; just report tile-pool savings.
kbot tnt lint --no-quality "metal heck.tnt"

# Override the sister .ota lookup (also accepts virtual paths).
kbot tnt lint --ota ./my-edit.ota "metal heck.tnt"

# Emit SARIF 2.1.0 on stdout for CI ingest (GitHub, GitLab, Harness…).
kbot tnt lint --ci "metal heck.tnt" > tnt-lint.sarif

Relative paths like ./maps/Metal Heck.tnt are resolved against the mounted VFS (lowercased and ./ stripped); any .. segment is rejected so a path argument can't escape the kbot workspace.

Checks (each emits a row, ✅ / ⚠️ / ❌ icon, one-line summary):

Group Rule Catches
Tile pool duplicate-tiles Byte-identical tile graphics
Tile pool similar-tiles Visually-similar graphics sharing a heightmap footprint (configurable via --similarity)
Tile pool unused-tiles Tile graphics no cell references
Quality dedupTiles Same as duplicate-tiles, surfaced via the Studio quality dialog id
Quality otaFields Lobby-required OTA metadata missing
Quality startsInBounds Start positions outside the map or sitting on a void cell
Quality schemaSlots An advertised numplayers count no schema can host (insufficient StartPos entries)
Quality metalProximity Start without a metal-producing feature within 24 tiles (skipped on metal-rich schemas)
Quality voidIslands Passable cells unreachable from any start (≥ 20 cells)
Quality heightDiscontinuities Cliffs > 32 height units between adjacent cells — likely to block ground pathing

The shared implementation lives in internal/maplint so the CLI, the studio dialog, and the MCP tool all run identical logic against identical thresholds. The MCP tool name is tnt_lint and accepts path, ota_path (optional), similarity (optional, defaults to 1.0), quality (optional, defaults to true), and game_data (optional, picks the VFS used for the metal-proximity check).


kbot zrb — Smacker Video

Work with Smacker (.smk/.zrb) video files.

# Show video information
kbot zrb info video.smk

# Convert to MP4
kbot zrb to-mp4 video.smk --target video.mp4

# Convert from MP4
kbot zrb from-mp4 video.mp4 --target video.smk

Requires FFmpeg for conversions.


kbot bik — Bink Video

Work with Bink (.bik) video files — the cutscene format TA: Kingdoms uses in place of the original game's Smacker (.zrb) videos. See docs/formats/bik.md for the format.

# Show video information (native header parse — no ffmpeg needed)
kbot bik info movies/takmission14_ph.bik

# Decode to MP4
kbot bik to-mp4 movies/takmission14_ph.bik intro.mp4

to-mp4 requires FFmpeg (which ships a Bink decoder). Conversion is decode-only — no open-source Bink encoder exists, so unlike kbot zrb there is no from-mp4 counterpart.


kbot mount — Asset Explorer

Browse game files interactively in a terminal.

# Terminal browser
kbot mount ~/ta-content

# Flatten (extract all files to disk)
kbot mount ~/ta-content flatten --target ./flat

# Omit the path to mount the active kbot context (see `kbot ctx`)
kbot mount flatten --target ./flat

Terminal commands: ls, cd, pwd, cat, describe, archives, stats, exit

For a graphical asset explorer, use the Files tab in kbot studio (see below). It browses the VFS with per-folder listings and adds rich per-format previews:

  • View GAF animations with APNG preview, sequence/frame controls, and transparency toggles
  • View PCX/FNT images with format options, and palettes as a swatch grid
  • Play SMK/ZRB/Bink video transcoded to MP4 with an animated poster
  • View TNT/SCT maps with minimap/heightmap/tilemap/buildmap/voidmap renders
  • Inspect COB disassembly/decompilation, TDF/FBI/OTA structure, and raw hex
  • Background cache warming with live websocket progress

kbot studio — KBot Studio (Web Workbench)

A browser-based workbench for Total Annihilation and TA: Kingdoms. It mounts a game install (or the active kbot ctx) over a VFS and opens a tabbed single-page app whose welcome screen offers several workflows.

# Open against a packed TA install
kbot studio ~/games/totala

# Or use the active kbot context (see `kbot ctx`)
kbot studio

# Custom port (default 8100)
kbot studio --port 9000

Explorer — the full Files tab / VFS browser: table and icon listings with live thumbnails, search, and a layer/source override, plus rich per-format previews — GAF/PCX/FNT/palette images, TNT/SCT maps, COB/BOS code (decompile, disassembly, call graph, lint), AI profiles, TDF/FBI/OTA structure, SMK/ZRB/BIK video, and audio — with a metadata, hex, and layering tab on every file and a download menu that offers rendered variants (PNG/GIF, MP4) alongside the raw bytes. A background cache warmer renders preview assets on startup and reports progress live over a websocket.

Map Creator — a visual TNT/OTA map editor. Start from a blank canvas or open an existing map, then paint terrain from .sct sections, place and scatter features, sculpt the heightmap, mark engine voids, and set per-schema start positions. Split panes, undo/redo, a ruler, and symmetry tools aid editing; a Quality Checker lints the map (with auto-fixes) before you save. The export menu bundles a downloadable .hpi (or loose TNT/OTA) plus full renders, minimaps, heightmaps, buildmaps, and voidmaps.

Sandbox Mode — drop units onto a battlefield and test them live. A WASM physics core simulates movement, commands, weapon fire, and damage with team colours and particle effects. Run a local in-browser scene, host an authoritative match, or join a live hosted sandbox for shared multiplayer testing.

Unit Creator — open a unit to inspect and test it: an orbiting 3D model viewer with per-piece animation and weapon hardpoints, a piece tree, texture and weapon galleries, and a COB/BOS thread debugger that steps scripts with synced source/assembly, locals, and runtime port values.

A Scripting workflow and further tools are previewed on the welcome screen as planned additions.


kbot document — Reference Catalogue Generator

Generate the markdown reference catalogues that live in two standalone sibling repos:

# Total Annihilation — uses the active kbot context for --source
kbot document --target ~/go/src/github.com/coreprime/reference-ta

# TA: Kingdoms
kbot document --game takingdoms \
              --source ~/tak-flat \
              --target ~/go/src/github.com/coreprime/reference-tak

# Explicit source + skip the portrait re-conversion (TA only — TA:K
# has no unitpics/ to convert)
kbot document --source ~/ta-flat --target ./reference --skip-portraits

# Force a fresh PCX → PNG batch (default skips if PNG already exists)
kbot document --target ./reference --force-portraits

Output layout under --target (TA):

<target>/
├── ta-units.md         278-row unit catalogue
├── ta-weapons.md       198-row weapon catalogue + reverse cross-ref
├── ta-buildtree.md     per-builder 2×3 menu grids + visual reverse index
└── img/ta-units/*.png  282 unit portraits (Objectname-keyed, lowercased)

Output layout under --target (TA: Kingdoms):

<target>/
├── tak-units.md        per-side unit catalogue (Aramon/Taros/Veruna/Zhon/Creon/Mon/Lif/NPC)
├── tak-weapons.md      inline [WEAPONn] sections harvested from every FBI
└── tak-buildtree.md    per-builder canbuild/ listings (linear Priority order)

Implementation lives in internal/documentor; text/template files are embedded under internal/documentor/templates/ (one set per game). The TA:K extractor (extract_tak.go) is separate from the TA extractor because TA:K inlines weapons in FBIs, has 4 sides (5 with Iron Plague), and uses canbuild/<builder>/*.tdf directories rather than gamedata/sidedata.tdf + download/*.tdf.

Convenience targets:

Task What it does
task document TA → ../reference-ta (uses $TA_UNPACKED_PATH)
task document-tak TA:K → ../reference-tak (uses $TAK_UNPACKED_PATH)

Note on output licensing. The kbot document tool itself is MIT-licensed (it ships in this repo). Its output — the rendered markdown tables and the copied/converted unit portraits — is derivative of Cavedog/Humongous game data and is not covered by kbot's MIT licence. The reference repos (coreprime/reference-ta / coreprime/reference-tak) intentionally ship without a LICENSE file for that reason; see their READMEs for the attribution notice.


kbot mcp — Model Context Protocol Server

Expose kbot's TA tooling to AI assistants (Claude Code, Claude Desktop, Cursor, etc.) over MCP. The assistant can then decompile scripts, lint COB, inspect HPI archives, render GAF/PCX, and so on directly against your game install.

# Register a TA install as a virtual filesystem (recommended)
kbot mcp --game-data ~/games/totala

# Name it explicitly and add a TA: Kingdoms install alongside
kbot mcp \
  --game-data totala=~/games/totala \
  --game-data kingdoms=~/games/tak

# Raw mount roots without the VFS layer (back-compat; assistant must pass absolute paths)
kbot mcp --mount ~/games/totala --mount /tmp/kbot-out

# Long-lived HTTP transport for multi-client setups
kbot mcp --http 127.0.0.1:8765 --game-data ~/games/totala

# Without --game-data or --mount, kbot mcp registers the active kbot
# context (see `kbot ctx`) as the default game-data folder
kbot mcp

--game-data NAME=PATH (or just PATH, name derived from the basename) registers a Total Annihilation / TA: Kingdoms install as a named virtual filesystem. The folder is walked once, every .hpi / .ufo / .ccx / .gp3 archive is opened, and contents are layered over physical files exactly as the game sees them. The first --game-data is the default the assistant uses when a tool call omits game_data. Each game-data base is added implicitly to the path guard, so on-disk paths inside it also resolve.

Once a game-data folder is configured, every tool's path argument accepts:

  • an absolute on-disk path (e.g. /Users/me/games/totala/units/ARMCOM.fbi),
  • a virtual path inside the VFS (e.g. units/ARMCOM.fbi), or
  • a bare filename (e.g. ARMCOM.bos) that the resolver searches for across every archive and physical file. Ambiguous matches return a list so the assistant can pick.

When a hit lives inside an archive, kbot extracts it to a temp file for the duration of the call and cleans up afterwards. Physical files are passed through directly.

--mount is the older, simpler gate: it just restricts paths the assistant passes to lie inside the given root, with no VFS layering. Use it when you want to expose a non-game-data directory (such as an output scratch dir) without the cost of walking and indexing archives. Without any --mount or --game-data, the server runs in permissive mode and accepts any absolute path — fine for local development, unsafe on shared hosts.

TNT render tools

These tools render various artefacts from a .tnt to PNG. Each accepts path (the .tnt argument, resolved through the same VFS rules as every other tool) and output (the destination PNG path):

Tool Purpose
tnt_image Full tile-grid render at 32 px per tile. Used by the studio's Export Full Render menu item.
tnt_heightmap 8-bit grayscale elevation grid (round-trip safe by default; normalize=true for human viewing).
tnt_buildmap Per-cell buildability classification — black/red/blue/yellow/green key (see the CLI section above). Optional sealevel overrides the .tnt header's value (0 disables the underwater check).
tnt_voidmap Engine-void mask — cells with Feature == 0xFFFC painted red, everything else transparent.
tnt_minimap Embedded 252×252 minimap (paletted PNG when paletted=true).
tnt_preview tnt_image plus composited feature sprites and numbered StartPos markers for the schema chosen by schema (0-based; defaults to 0). Requires game_data so feature sprites and the sister .ota resolve.
TAF / TSF animation tools

These tools inspect and render TA: Kingdoms truecolor animations (see docs/formats/taf.md). taf_render and taf_sheet return the rendered PNG inline so the assistant can show it directly; taf_export writes an animated file to output.

Tool Purpose
taf_info Per-frame JSON: name, size, origin, pixel format and duration for every frame.
taf_list One-line JSON summary (name, frame count, duration, format).
taf_render Render a single frame and return it inline as PNG (optional output also saves it).
taf_sheet Render every frame into a grid sprite sheet, returned inline as PNG.
taf_export Write an animated GIF (format: gif) or APNG (format: apng/png, default) to output.
taf_lint Structural validation findings as JSON.
tsf_info Summarise a TSF document — animation, frames and referenced layer images.
tsf_lint Shape-check a TSF document against the compiler's expectations.
Bink video tools

These tools inspect and convert TA: Kingdoms Bink cutscenes (see docs/formats/bik.md).

Tool Purpose
bik_info Header JSON: geometry, frame count, frame rate, duration, alpha/grayscale flags and per-track audio. Native parse, no ffmpeg.
bik_to_mp4 Decode a .bik to MP4 (H.264/AAC) via ffmpeg and write it to output. Decode-only — no reverse conversion exists.
Smacker video tools

These tools inspect and convert the original TA Smacker/ZRB cutscenes (see docs/formats/smacker.md).

Tool Purpose
zrb_info Header JSON: signature, geometry, frame count, frame rate, duration and present audio tracks. Native parse, no ffmpeg.
zrb_to_mp4 Decode a .zrb/.smk to MP4 (H.264/AAC) via ffmpeg and write it to output.
zrb_from_mp4 Re-encode an MP4 back to Smacker via ffmpeg's smackvid/smackaud encoders (best-effort; not in every ffmpeg build).
Scenario tools
Tool Purpose
crt_describe Summarise a TA: Kingdoms .crt scenario: placed-unit breakdown, per-player rule counts and named trigger regions, returned as JSON. Pass units: true for the full per-unit placement list.
VFS introspection tools

These tools let the assistant query the virtual filesystem directly:

Tool Purpose
vfs_game_data List registered game-data folders, their base paths, archive counts and file counts.
vfs_find Locate files by bare name (ARMCOM.bos), basename glob (*.bos) or full-path glob (units/ARM*.fbi). Returns the virtual path and the source archive for every hit.
vfs_list List virtual files and subdirectories under a given directory.
vfs_stat Show every layer that contains a file — useful when a mod overrides a base-game asset.

So the user can ask "where is ARMCOM.bos?" and the assistant calls vfs_find with query: "ARMCOM.bos".

Configuring with Claude Code

Register kbot as a user-scoped MCP server so it loads in every session:

claude mcp add -s user kbot kbot mcp --game-data /path/to/total-annihilation

If the claude CLI is not on your PATH, add the entry directly to ~/.claude.json:

{
  "mcpServers": {
    "kbot": {
      "type": "stdio",
      "command": "kbot",
      "args": [
        "mcp",
        "--game-data",
        "/path/to/total-annihilation"
      ]
    }
  }
}

Restart any open Claude Code sessions; new sessions will pick up the server automatically. Use /mcp inside Claude Code to verify the connection and list the available tools.

For Claude Desktop, add the same mcpServers block to its config file (~/Library/Application Support/Claude/claude_desktop_config.json on macOS) and restart the app.


Shell Completion

kbot supports tab completion for all commands, subcommands, and flags.

Zsh (macOS default)
# Generate and install (uses brew --prefix to find the correct path):
kbot completion zsh > $(brew --prefix)/share/zsh/site-functions/_kbot

# Ensure your .zshrc includes:
autoload -Uz compinit && compinit

Note: On Apple Silicon Macs (M1/M2/M3), Homebrew installs to /opt/homebrew. On Intel Macs, it's /usr/local. Using $(brew --prefix) handles both automatically.

You may need to restart your terminal or run exec zsh for changes to take effect.

Bash (macOS with bash)
# Requires bash-completion (install via Homebrew if needed):
brew install bash-completion@2

# Generate and install:
kbot completion bash > $(brew --prefix)/etc/bash_completion.d/kbot

# Ensure your .bash_profile includes:
[[ -r "$(brew --prefix)/etc/profile.d/bash_completion.sh" ]] && . "$(brew --prefix)/etc/profile.d/bash_completion.sh"
Fish
kbot completion fish > ~/.config/fish/completions/kbot.fish
PowerShell
kbot completion powershell | Out-String | Invoke-Expression

# Or save permanently:
kbot completion powershell > kbot.ps1
# Add `. /path/to/kbot.ps1` to your PowerShell profile

Project Structure

kbot/
├── cmd/kbot/        CLI entry point and subcommands
├── formats/         Public format packages
│   ├── ai/          AI opponent profiles
│   ├── bik/         Bink video (TA:K cutscenes; header parse + ffmpeg decode)
│   ├── crt/         TA:K scenario sidecars (placed units, rules, triggers)
│   ├── 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 compiler
│   │   ├── decompiler/  COB → BOS decompiler
│   │   ├── linter/      Static analysis (17 rules)
│   │   └── parser/      Lexer, parser, preprocessor
│   ├── sct/         Map sections
│   ├── smacker/     Smacker video
│   ├── tdf/         Text data files
│   ├── tdo/         3DO models
│   └── tnt/         Map terrain
├── filesystem/      Virtual filesystem (HPI layering)
└── internal/
    ├── assetrender/ Shared VFS render/describe engine (studio Files tab)
    ├── assets/      Embedded TA palette
    ├── cache/       On-disk file cache
    ├── mount/       Terminal asset browser (kbot mount)
    ├── studio/      Web workbench + Files explorer (kbot studio)
    └── testutil/    Asset-path helpers for tests

Development

task              # build + vet + lint + test
task build        # build Studio web assets + kbot binary
task install      # install kbot to $GOPATH/bin
task test         # run all tests
task test-race    # tests with race detector
task lint         # golangci-lint + eslint
task coverage     # generate coverage.html
Prerequisites
  • Go 1.24+
  • Node.js 18+ and npm (for the Studio web assets)
  • Task (go install github.com/go-task/task/v3/cmd/task@latest)
  • golangci-lint (for task lint)
  • FFmpeg (optional, for video conversions)
Game Asset Setup

Many tests require a copy of the Total Annihilation game assets. You need:

  1. Original game installation — from GOG, Steam, or original CD media (version 3.1c recommended)
  2. Flattened (unpacked) assets — extracted from the HPI/UFO/CCX archives

Use kbot mount flatten to extract the packed archives into a flat directory:

# First build kbot
task build

# Flatten the game assets (adjust the source path to your TA install location)
./bin/kbot mount ./path/to/total-annihilation --flatten --target ./ta-flattened

Then create a .env.local file in the project root (this file is gitignored):

# .env.local — local paths to game assets for tests
TA_UNPACKED_PATH=/path/to/ta-flattened
TA_PACKED_PATH=/path/to/total-annihilation
TAK_UNPACKED_PATH=/path/to/ta-kingdoms-flattened
Variable Description Used by
TA_UNPACKED_PATH Flattened TA game assets (scripts, maps, textures, etc.) Most format tests
TA_PACKED_PATH Original packed TA archives (HPI, UFO, CCX, GP3) HPI/VFS tests
TAK_UNPACKED_PATH Flattened TA: Kingdoms assets TA:K format, adapter, and combat tests
ALLOW_SKIP_ASSETS If true, tests skip when assets are missing. If false (default), tests fail. All asset tests

By default, missing game assets cause test failures to ensure developers have a complete test environment. Set ALLOW_SKIP_ASSETS=true in CI or environments where game assets are not available.

Supported Games & Custom Games

kbot supports Total Annihilation and Total Annihilation: Kingdoms. The format packages (formats/…) are game-agnostic — a TNT or COB announces its own dialect through version words and parsing dispatches on what the bytes say. Everything that instead depends on which game an install is lives behind a per-game adapter, registered in two registries:

  • Gogames defines the Game/Adapter interfaces (palette resolution, unit sounds, cursor palettes, tilesets, map terrain groups, FBI→unit-meta building). games/totala and games/takingdoms register themselves from package init; sessions resolve one adapter per mounted install via games.Resolve(id).
  • JS@kbot/game-totala and @kbot/game-takingdoms carry the studio's per-game surfaces: weapon-script conventions, COB quick-action catalogues, branding, welcome theming, and the view3d tables injected into the game-agnostic @kbot/game3d renderer at boot. The studio resolves the active adapter through ui/common/game-registry.js.

Engine behaviour that varies per unit rather than per game — TA's per-slot AimPrimary/FirePrimary scripts versus TA:K's shared parameterized AimWeapon/FireWeapon set — is resolved from what each COB actually exports (engine/sim/weapon_convention.go), so the simulation never consults a game id and mixed-convention content just works.

To build a custom game on the TA formats:

  1. Go: implement games.Game (usually embedding the totala adapter and overriding what differs), call games.Register from your package init, and blank-import the package where the studio wires adapters (internal/studio/palette.go).
  2. JS: create a package composing over @kbot/game-totala the way @kbot/game-takingdoms does (spread the baseline game object, override fields), and add it to the registry list in internal/studio/web/ui/common/game-registry.js. The required surface is pinned by the contract tests in packages-js/game-takingdoms/contract.test.js (task test-js).
  3. Context: register the install with kbot ctx add <path> --game custom. Unregistered ids resolve to the Total Annihilation baseline in both registries, so a partially-adapted game degrades to TA behaviour instead of failing.

History

Having played Total Annihilation since its release in 1996 religiously, it was one of the formative games of my youth. Back in 1999, a 14 year old version of me logged onto the PlanetAnnihilation forums. They were dead, quiet. I was annoyed that there was lots of modding and activity going on, but nobody was updating the site, similar to how PlanetQuake/PlanetHalfLife were being maintained well. With poor wording, a young version of me unleashed some harsh words.

Instead of telling me to get stuffed, Frank "DMFrank" Rogan responded. He basically offered me the keys to the site, and said if I wanted it that badly, I can give it a go. I spent the next few years learning how to write editorial content, connecting with mod developers, the basics of software engineering to build new features for the site. A few years of building a community, and promoting the work of other developers was good for the soul.

Then life, or more specifically University hit. That plus the age of the game meant that the rate of new and interesting things slowed down. I went away for a while. I relocated to Australia from the UK. Whilst I never really disappeared or stop reading the forums, tracking projects, I no longer had time to be present/engage with them.

I came back in 2014, having realised I could reverse engineer the network protocols behind the defunct Boneyards.net game service a journey I partially documented over at TAUniverse. However it was an immense amount of work, and between trialling out one or two folks to help, none of whom had the means or ability to really lighten the load - the project again, succumbed to life.

Speaking with one of my colleagues at work, the idea came up to try this again - but with the power of AI to help drive the process. Part of the work for rebuilding Boneyards.net involves lots of work with the TA game formats, such as creating new Galactic maps, packing them into HPI files, being able to reason about the planets/maps being referenced.

This project is an open-source implementation of the various formats. I have archived some of these files in another repository. A longstanding bug-bear of mine was the incomplete or tricky to use tooling, so I've endevoured to make these as robust as I can and will continue to polish them.

License

© Steve Gray

The code is released under the MIT License — see LICENSE for details.

Directories

Path Synopsis
cmd
engine-wasm command
kbot command
engine
assets
Package assets defines the boundary through which the otherwise-pure simulation obtains unit definitions and scripts.
Package assets defines the boundary through which the otherwise-pure simulation obtains unit definitions and scripts.
fixed
Package fixed provides a deterministic Q16.16 fixed-point number type and the trigonometric/root primitives the simulation needs.
Package fixed provides a deterministic Q16.16 fixed-point number type and the trigonometric/root primitives the simulation needs.
frame
Package frame defines the render snapshot the local engine instance hands to the renderer each visual frame.
Package frame defines the render snapshot the local engine instance hands to the renderer each visual frame.
order
Package order defines the player commands a client submits to the simulation.
Package order defines the player commands a client submits to the simulation.
rng
Package rng is the deterministic PRNG every random draw in the simulation routes through.
Package rng is the deterministic PRNG every random draw in the simulation routes through.
script
Package script is the deterministic COB bytecode virtual machine.
Package script is the deterministic COB bytecode virtual machine.
session
Package session is the transport-agnostic driver that wraps a simulation world and turns submitted orders into stepped ticks and render snapshots.
Package session is the transport-agnostic driver that wraps a simulation world and turns submitted orders into stepped ticks and render snapshots.
sim
Package sim is the headless, deterministic Total Annihilation simulation core.
Package sim is the headless, deterministic Total Annihilation simulation core.
wire
Package wire defines the client/server protocol the websocket transport carries.
Package wire defines the client/server protocol the websocket transport carries.
Package filesystem provides virtual filesystem abstraction for TA archives.
Package filesystem provides virtual filesystem abstraction for TA archives.
formats
ai
bik
Package bik reads RAD Game Tools Bink (.bik) video headers — the cutscene format TA: Kingdoms uses in place of the Smacker (.zrb) videos shipped with the original Total Annihilation.
Package bik reads RAD Game Tools Bink (.bik) video headers — the cutscene format TA: Kingdoms uses in place of the Smacker (.zrb) videos shipped with the original Total Annihilation.
crt
Package crt decodes Total Annihilation: Kingdoms .crt scenario files.
Package crt decodes Total Annihilation: Kingdoms .crt scenario files.
fnt
Package fnt implements reading of Total Annihilation bitmap font files.
Package fnt implements reading of Total Annihilation bitmap font files.
gaf
Package gaf implements reading and writing of Total Annihilation and TA: Kingdoms GAF (Graphics Animation Format) files.
Package gaf implements reading and writing of Total Annihilation and TA: Kingdoms GAF (Graphics Animation Format) files.
gamedata/common
Package common holds the game-data structures shared by Total Annihilation and TA:Kingdoms.
Package common holds the game-data structures shared by Total Annihilation and TA:Kingdoms.
gamedata/ta
Package ta provides typed Go representations of Total Annihilation game-data files (TDF/FBI/GUI), built on the github.com/coreprime/kbot/formats/tdf codec.
Package ta provides typed Go representations of Total Annihilation game-data files (TDF/FBI/GUI), built on the github.com/coreprime/kbot/formats/tdf codec.
gamedata/tak
Package tak provides typed Go representations of Total Annihilation: Kingdoms game-data files, built on the github.com/coreprime/kbot/formats/tdf codec.
Package tak provides typed Go representations of Total Annihilation: Kingdoms game-data files, built on the github.com/coreprime/kbot/formats/tdf codec.
hpi
Package hpi provides version-agnostic reading of Total Annihilation and TA: Kingdoms HPI archives.
Package hpi provides version-agnostic reading of Total Annihilation and TA: Kingdoms HPI archives.
hpi/v1
Package v1 reads and writes Total Annihilation HPI archives (version 0x00010000).
Package v1 reads and writes Total Annihilation HPI archives (version 0x00010000).
hpi/v2
Package v2 reads TA: Kingdoms HPI archives (version 0x00020000).
Package v2 reads TA: Kingdoms HPI archives (version 0x00020000).
objects3d
Package objects3d implements reading of Total Annihilation 3DO model files.
Package objects3d implements reading of Total Annihilation 3DO model files.
pal
Package pal implements reading and writing of Total Annihilation .PAL palette files.
Package pal implements reading and writing of Total Annihilation .PAL palette files.
pcx
Package pcx provides support for reading and writing PCX image files.
Package pcx provides support for reading and writing PCX image files.
scripting/linter
Package linter provides static analysis rules for COB/BOS scripts.
Package linter provides static analysis rules for COB/BOS scripts.
sct
Package sct implements reading of Total Annihilation SCT (Section) map files.
Package sct implements reading of Total Annihilation SCT (Section) map files.
tdf
Package tdf implements reading and writing of Total Annihilation TDF/FBI files.
Package tdf implements reading and writing of Total Annihilation TDF/FBI files.
tnt
Package tnt implements reading of Total Annihilation TNT map files.
Package tnt implements reading of Total Annihilation TNT map files.
tnt/tak
Package tak implements read/write for the TA: Kingdoms (0x4000) variant of the TNT map container.
Package tak implements read/write for the TA: Kingdoms (0x4000) variant of the TNT map container.
tsf
Package tsf implements reading, writing, compiling and decompiling of Total Annihilation: Kingdoms truecolor animation files.
Package tsf implements reading, writing, compiling and decompiling of Total Annihilation: Kingdoms truecolor animation files.
Package games is the seam between kbot's shared TA-format machinery and the behaviour that genuinely differs per game.
Package games is the seam between kbot's shared TA-format machinery and the behaviour that genuinely differs per game.
takingdoms
Package takingdoms implements games.Game for Total Annihilation: Kingdoms.
Package takingdoms implements games.Game for Total Annihilation: Kingdoms.
totala
Package totala implements games.Game for Total Annihilation: one global palette for every asset class, sound events from gamedata/sound.tdf, and the classic fixed world list for the map editor.
Package totala implements games.Game for Total Annihilation: one global palette for every asset class, sound events from gamedata/sound.tdf, and the classic fixed world list for the map editor.
internal
assetrender
Package assetrender turns Total Annihilation game files into the byte streams a browser can display: raw passthrough, format-specific "describe" metadata, and image/video renderings.
Package assetrender turns Total Annihilation game files into the byte streams a browser can display: raw passthrough, format-specific "describe" metadata, and image/video renderings.
documentor
Package documentor extracts reference data from a flattened Total Annihilation install and renders the markdown catalogues consumed by the standalone github.com/coreprime/reference-ta repo.
Package documentor extracts reference data from a flattened Total Annihilation install and renders the markdown catalogues consumed by the standalone github.com/coreprime/reference-ta repo.
gameserver
Package gameserver hosts authoritative game simulations and relays their command stream to connected clients over a transport.
Package gameserver hosts authoritative game simulations and relays their command stream to connected clients over a transport.
kbotctx
Package kbotctx manages named working-directory contexts for kbot.
Package kbotctx manages named working-directory contexts for kbot.
maplint
Package maplint runs the studio's quality checks against a parsed TNT map + OTA metadata, returning a list of diagnostics.
Package maplint runs the studio's quality checks against a parsed TNT map + OTA metadata, returning a list of diagnostics.
mcp
Package mcp exposes kbot's TA format tooling over the Model Context Protocol so that AI assistants (Claude Desktop, Cursor, etc.) can decompile, lint, inspect and extract Total Annihilation assets.
Package mcp exposes kbot's TA format tooling over the Model Context Protocol so that AI assistants (Claude Desktop, Cursor, etc.) can decompile, lint, inspect and extract Total Annihilation assets.
mount
Package mount provides the `kbot mount` interactive terminal browser for Total Annihilation archives.
Package mount provides the `kbot mount` interactive terminal browser for Total Annihilation archives.
palettepick
Package palettepick resolves which TA / TA: Kingdoms palette should be used to render a given GAF.
Package palettepick resolves which TA / TA: Kingdoms palette should be used to render a given GAF.
studio
Selection-hotkey API.
Selection-hotkey API.
testutil
Package testutil provides helpers for tests that depend on TA game assets.
Package testutil provides helpers for tests that depend on TA game assets.
tntpreview
Package tntpreview composites the "preview" view of a Total Annihilation TNT map: the base tile-grid render with feature sprites painted on top and numbered start-position markers drawn from the sister .ota.
Package tntpreview composites the "preview" view of a Total Annihilation TNT map: the base tile-grid render with feature sprites painted on top and numbered start-position markers drawn from the sister .ota.
workspace
Package workspace models an editable KBot Studio workspace: a writable work folder overlaid on top of a kbot context (and that context's parent chain).
Package workspace models an editable KBot Studio workspace: a writable work folder overlaid on top of a kbot context (and that context's parent chain).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL