tnt

package
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 Imports: 17 Imported by: 0

Documentation

Overview

Package tnt implements reading of Total Annihilation TNT map files.

Index

Constants

View Source
const (
	VersionTA  = 8192  // 0x2000 — Total Annihilation
	VersionTAK = 16384 // 0x4000 — Total Annihilation: Kingdoms
)

TNT IDVersion words. TA writes 0x2000; TA: Kingdoms reuses the TNT container with a bumped version word and a different field layout.

View Source
const HeaderSize = 64

HeaderSize is the on-disk size of the TNT header in bytes.

View Source
const MinimapVoidByte = 0x64

MinimapVoidByte is the palette index used for minimap padding (outside map area).

View Source
const TAKDataUnit = 16

TAKDataUnit is the pixel size of a TA: Kingdoms DataUnit — the unit of the heightmap and feature-placement grids. Feature placements scale by this to reach full-resolution terrain pixels.

View Source
const TileAnimEntrySize = 132

TileAnimEntrySize is the on-disk size of a tile animation/feature entry.

View Source
const TileGfxSize = 32 * 32

TileGfxSize is the byte size of a single 32×32 tile graphic.

Variables

This section is empty.

Functions

func Pack

func Pack(dir string) (*Map, []Feature, error)

Pack reads a directory written by Unpack and reconstructs the Map + Feature table. Callers can then call Map.Save to write out a fresh TNT.

func Unpack

func Unpack(m *Map, features []Feature, palette color.Palette, dir string) error

Unpack is shorthand for UnpackWithOptions with Lossless=true, preserving the historical byte-identical round-trip guarantee.

func UnpackWithOptions

func UnpackWithOptions(m *Map, features []Feature, palette color.Palette, dir string, opts UnpackOptions) error

UnpackWithOptions writes m and the supplied feature table into dir as a directory of editable artefacts:

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 32x32 PNG per unique tile
tilemap.csv        2D grid of tile indices
features.csv       feature_index,name,attr_x,attr_y per placement
metadata.json      header constants + feature table + round-trip info

dir is created if missing.

func WritePNG

func WritePNG(w io.Writer, img image.Image) error

WritePNG encodes an image to PNG format.

Types

type Feature

type Feature struct {
	Index int
	Name  string
	Raw   [128]byte
}

Feature is a named feature type from the TileAnim table. Raw preserves the full 128-byte name buffer including any uninitialised scratch memory past the null terminator, so the table can round-trip byte-for-byte. When Raw is empty the writer falls back to writing Name zero-padded.

type FeatureMarker

type FeatureMarker struct {
	X     int    `json:"x"`
	Y     int    `json:"y"`
	Value uint16 `json:"value"`
}

FeatureMarker records a cell whose feature column holds a non-placement sentinel value (commonly 0xFFFE "void" or 0xFFFC seen on early maps).

type FeaturePlacement

type FeaturePlacement struct {
	FeatureIdx int // Index into Features
	AttrX      int // Attribute cell X (16px units)
	AttrY      int // Attribute cell Y (16px units)
	PixelX     int // Pixel X (AttrX * 16)
	PixelY     int // Pixel Y (AttrY * 16)
}

FeaturePlacement is a placed feature instance on the map.

type Header struct {
	IDVersion   uint32 // 0x2000 (TA) or 0x4000 (TA:K)
	Width       uint32 // TA: width in 16px attribute cells (tiles = Width/2). TA:K: width in 16px DataUnits.
	Height      uint32 // TA: height in 16px attribute cells (tiles = Height/2). TA:K: height in 16px DataUnits.
	PTRMapData  uint32 // TA: tile index array. TA:K: sea level.
	PTRMapAttr  uint32 // TA: attribute array. TA:K: heightmap (Width×Height bytes).
	PTRTileGfx  uint32 // TA: tile graphics. TA:K: attribute/feature grid (Width×Height uint16).
	Tiles       uint32 // TA: number of unique tiles. TA:K: feature name table offset.
	TileAnims   uint32 // TA: number of feature entries. TA:K: feature count.
	PTRTileAnim uint32 // TA: feature structures. TA:K: terrain-name table (guW×guH uint32).
	SeaLevel    uint32 // TA: sea level. TA:K: U-mapping table (guW×guH bytes).
	PTRMinimap  uint32 // TA: minimap (252×252). TA:K: V-mapping table (guW×guH bytes).
	Unknown1    uint32 // TA:K: minimap pointer (126×126 block at offset 0x2c).
	Pad1        uint32
	Pad2        uint32
	Pad3        uint32
	Pad4        uint32
}

Header is the 64-byte TNT file header.

The field names describe the Total Annihilation (0x2000) layout. TA: Kingdoms (0x4000) keeps the same 64-byte size but moves several fields: notably its minimap pointer lands in the Unknown1 slot (offset 0x2c).

type HeaderMetadata

type HeaderMetadata struct {
	IDVersion uint32 `json:"id_version"`
	SeaLevel  uint32 `json:"sea_level"`
	Unknown1  uint32 `json:"unknown1"`
	Pad1      uint32 `json:"pad1"`
	Pad2      uint32 `json:"pad2"`
	Pad3      uint32 `json:"pad3"`
	Pad4      uint32 `json:"pad4"`
}

HeaderMetadata captures the round-trip header fields that aren't derivable from the other unpacked artefacts.

type LintDiagnostic

type LintDiagnostic struct {
	Rule       string       `json:"rule"`
	Severity   LintSeverity `json:"severity"`
	Message    string       `json:"message"`
	Count      int          `json:"count"`
	BytesSaved int          `json:"bytes_saved"`
}

LintDiagnostic is one finding from Map.Lint. Count and BytesSaved duplicate information that Message also contains in human form so that the explorer can render badges, sort, and filter without parsing the Message string.

type LintOptions

type LintOptions struct {
	// SimilarityPercent is the maximum mean per-channel pixel difference
	// (% of 255) for the similar-tiles rule.  Set to 0 to skip the
	// similarity check.  Required > 0 also implies Palette must be set.
	SimilarityPercent float64

	// Palette converts paletted tile bytes to RGB when scoring similarity.
	// Required when SimilarityPercent > 0.
	Palette color.Palette
}

LintOptions tunes Map.Lint.

type LintSeverity

type LintSeverity string

LintSeverity classifies a TNT lint finding. TNT lints currently describe size-reduction opportunities so all findings are LintInfo; the type is kept severity-shaped to match the COB linter and keep the explorer's lint UI uniform across formats.

const (
	// LintInfo is an informational finding — typically a hint that
	// kbot tnt optimize could remove some redundancy.
	LintInfo LintSeverity = "info"
	// LintWarning is a potentially-impactful finding.
	LintWarning LintSeverity = "warning"
)

type Map

type Map struct {
	Header Header
	IsTAK  bool // true when the file is a TA: Kingdoms TNT (IDVersion 0x4000)

	TileW    int        // Tile grid width (Header.Width / 2)
	TileH    int        // Tile grid height (Header.Height / 2)
	AttrW    int        // Attribute grid width (Header.Width)
	AttrH    int        // Attribute grid height (Header.Height)
	TileMap  []uint16   // TileW × TileH tile indices
	TileAttr []TileAttr // AttrW × AttrH attributes (16px resolution)
	Tiles    [][]byte   // Tile graphics, each 1024 bytes
	Minimap  []byte     // Minimap palette indices (or nil)
	MinimapW int
	MinimapH int

	// TA: Kingdoms data. TA:K does not store a TA-style tile mosaic. Terrain is
	// texture-mapped: a grid of 32px Graphic Units, each naming a JPG texture
	// plus a U/V offset into it. A separate heightmap and feature grid sit at
	// DataUnit (16px) resolution. These are populated only when IsTAK is true.
	TAKW            int      // Width in 16px DataUnits (Header.Width)
	TAKH            int      // Height in 16px DataUnits (Header.Height)
	TAKGUW          int      // Graphic-Unit grid width  (TAKW/2; 32px units)
	TAKGUH          int      // Graphic-Unit grid height (TAKH/2; 32px units)
	TAKHeight       []byte   // TAKW×TAKH heightmap (one byte per DataUnit)
	TAKFeatureGrid  []uint16 // TAKW×TAKH feature indices (>=takNoFeature = none)
	TAKTerrainNames []uint32 // TAKGUW×TAKGUH terrain texture names ("%08X.JPG")
	TAKUMap         []byte   // TAKGUW×TAKGUH texture column offsets (32px units)
	TAKVMap         []byte   // TAKGUW×TAKGUH texture row offsets (32px units)
	// TAKFeatureTableRaw is the feature-name table captured verbatim (its
	// internal layout isn't modelled). SaveTAK writes it back unchanged so a
	// map with features round-trips; the entry count lives in Header.TileAnims.
	TAKFeatureTableRaw []byte

	// MapDataPad preserves any padding bytes between the end of the
	// tile-index array and the start of the attribute array.  Cavedog's
	// authoring tools pad the mapdata block to a 16-byte boundary (with
	// scratch memory), so we capture it verbatim for byte-perfect
	// round-trip.  May be empty.
	MapDataPad []byte
}

Map is a parsed TNT file.

func LoadFromReader

func LoadFromReader(r io.ReadSeeker) (*Map, error)

LoadFromReader parses a TNT file.

func (*Map) FeatureCounts

func (m *Map) FeatureCounts() map[int]int

FeatureCounts tallies placement counts keyed by feature index.

func (*Map) GetFeaturePlacements

func (m *Map) GetFeaturePlacements() []FeaturePlacement

GetFeaturePlacements returns all placed features from the MapAttr grid.

func (*Map) Lint

func (m *Map) Lint(opts LintOptions) ([]LintDiagnostic, error)

Lint inspects the map for size-reduction opportunities without mutating it. Each rule mirrors a pass of Map.Optimize:

duplicate-tiles  tile graphics with byte-identical pixel data
similar-tiles    visually-similar tile graphics whose placements
                 share the same heightmap footprint
unused-tiles     tile graphics that no map cell references

Lint runs Optimize on a deep copy of m, so the caller's map is left untouched and the reported counts match exactly what `kbot tnt optimize` would remove.

func (*Map) LoadFeatures

func (m *Map) LoadFeatures(r io.ReadSeeker) ([]Feature, error)

LoadFeatures reads the feature name table. TA stores the table pointer at 0x20 (PTRTileAnim); TA:K stores it at 0x18. Both use the same count field (0x1c) and the same 4-byte-index + 128-byte-name entry layout.

func (*Map) MinimapContentBounds

func (m *Map) MinimapContentBounds() (contentW, contentH int)

MinimapContentBounds returns the actual content area within the minimap, excluding the void padding (palette index 0x64) on the right and bottom.

func (*Map) Optimize

func (m *Map) Optimize(opts OptimizeOptions) (OptimizeStats, error)

Optimize rewrites m in-place to collapse redundant tile graphics.

Three passes run in order:

  1. byte-identical tile graphics are merged into a single index;
  2. visually-similar tile graphics whose placements share the same heightmap footprint are merged (skipped when SimilarityPercent is 0);
  3. tile graphics that no longer have any placement are removed (skipped when KeepUnused is true).

The on-disk heightmap and feature placements are preserved verbatim; only m.Tiles and m.TileMap are rewritten.

func (*Map) RenderASCII

func (m *Map) RenderASCII(maxCols int) string

RenderASCII produces a compact ASCII visualization of the height map, scaled down to fit within the given column width. The aspect ratio of the source is preserved (with a 2:1 character cell adjustment so columns read squarish in a terminal). TA:K maps render from the DataUnit heightmap at the same 16px cell resolution.

func (*Map) RenderBuildMap

func (m *Map) RenderBuildMap(seaLevel uint32) *image.RGBA

RenderBuildMap produces an attribute-resolution RGBA image showing per-cell buildability. Each cell is classified, in priority order:

void          Feature == 0xFFFC (canonical engine-void sentinel)
feature       Feature is a valid index in the .tnt feature table
underwater    Height < seaLevel (the cell would be submerged)
cliff         max |Δheight| to a 4-neighbour exceeds the cliff
              threshold — TA's pathing blocks traversal so no
              build either
buildable     otherwise

0xFFFD / 0xFFFE are deliberately not treated as void — see docs/formats/tnt.md. When seaLevel is 0 the underwater check is skipped (matches a map authoring tool that never wrote a sea level). Returns nil when the map has no attribute grid.

func (*Map) RenderHeightMap

func (m *Map) RenderHeightMap() *image.Gray

RenderHeightMap renders elevation data as a normalized greyscale image. The image is AttrW × AttrH (16px resolution, 2× the tile grid). TA:K maps render from their DataUnit heightmap at the same 16px resolution.

func (*Map) RenderHeightMapRaw

func (m *Map) RenderHeightMapRaw() *image.Gray

RenderHeightMapRaw returns an unflipped, unnormalized 8-bit grayscale image of the height attribute grid. Pixel value equals the raw elevation byte at that cell — suitable for byte-perfect round-trip through PNG.

Pixel(x, y) = TileAttr[y*AttrW+x].Height. TA:K maps render their DataUnit heightmap bytes directly — the same 16px cell resolution.

func (*Map) RenderMinimap

func (m *Map) RenderMinimap(palette color.Palette) *image.RGBA

RenderMinimap renders the minimap as an RGBA image. Void pixels (palette index 0x64) are rendered as transparent.

func (*Map) RenderMinimapPaletted

func (m *Map) RenderMinimapPaletted(palette color.Palette) *image.Paletted

RenderMinimapPaletted returns the minimap as a paletted image preserving the original palette indices, including the void/padding byte. Returns nil when no minimap is present.

func (*Map) RenderTAKHeightmap

func (m *Map) RenderTAKHeightmap() *image.Gray

RenderTAKHeightmap renders the TA: Kingdoms heightmap as a normalised greyscale image at DataUnit resolution (TAKW×TAKH). It needs no external assets, so it is the self-contained fallback when terrain textures are unavailable.

func (*Map) RenderTAKTerrain

func (m *Map) RenderTAKTerrain(tex func(name uint32) image.Image) *image.RGBA

RenderTAKTerrain composites the full-resolution TA: Kingdoms terrain by copying a 32×32 tile from each Graphic Unit's source texture at its U/V offset. tex resolves a terrain name to its decoded texture image (the caller supplies the JPGs, typically from a VFS); a nil return leaves that unit's tile blank so missing textures are visible rather than fatal. Returns nil when the map carries no terrain-name table.

func (*Map) RenderTileMap

func (m *Map) RenderTileMap(palette color.Palette) *image.RGBA

RenderTileMap renders the full map as an RGBA image.

func (*Map) RenderTilePaletted

func (m *Map) RenderTilePaletted(index int, palette color.Palette) *image.Paletted

RenderTilePaletted returns a single 32×32 tile as a paletted image using the given palette. Returns nil for out-of-range indices.

func (*Map) RenderVoidMap

func (m *Map) RenderVoidMap() *image.RGBA

RenderVoidMap produces an attribute-resolution RGBA image with the engine-void cells (Feature == 0xFFFC) painted opaque-red and every other cell transparent. See Map.RenderBuildMap for why 0xFFFD / 0xFFFE are not treated as void. Returns nil when the map has no attribute grid.

func (*Map) Save

func (m *Map) Save(w io.Writer, features []Feature) error

Save writes the map to a TNT byte stream. The features slice supplies the TileAnim feature table that ships with the TNT (the names referenced by TileAttr.Feature indices). Pass nil if there are no features.

Block layout produced:

0x00            header (64 B)
PTRMapData      tile index array  (TileW × TileH × uint16)
PTRMapAttr      attribute array   (AttrW × AttrH × 4 B)
PTRTileGfx      tile graphics     (Tiles × 1024 B)
PTRTileAnim     feature table     (Features × 132 B)
PTRMinimap      minimap           (8 B header + MinimapW × MinimapH)

func (*Map) SaveTAK

func (m *Map) SaveTAK(w io.Writer) error

SaveTAK writes a TA: Kingdoms (0x4000) TNT by delegating to the tak subpackage (which owns the 0x4000 read/write variance). It builds the subpackage's map view from the shared Map's TAK* fields — including the sea-level value and header padding preserved from the original header — so an edited heightmap / terrain-name / U-V / feature grid round-trips.

func (*Map) TAKFeaturePlacements

func (m *Map) TAKFeaturePlacements() []FeaturePlacement

TAKFeaturePlacements returns every placed feature in a TA: Kingdoms map, read from the DataUnit-resolution feature grid. PixelX/PixelY scale the DataUnit cell (16px) up to the full-resolution terrain render, where feature sprites are anchored.

func (*Map) TAKPixelH

func (m *Map) TAKPixelH() int

func (*Map) TAKPixelW

func (m *Map) TAKPixelW() int

TAKPixelW and TAKPixelH report the full-resolution terrain render dimensions in pixels (Graphic-Unit grid × 32px).

type Metadata

type Metadata struct {
	Version          string          `json:"version"`
	Header           HeaderMetadata  `json:"header"`
	Minimap          MinimapMetadata `json:"minimap"`
	Features         []string        `json:"features,omitempty"`
	FeatureRawB64    []string        `json:"feature_raw_b64,omitempty"`
	FeatureSentinels []FeatureMarker `json:"feature_sentinels,omitempty"`
	AttrPadB64       string          `json:"attr_pad_b64,omitempty"`
	MapDataPadB64    string          `json:"map_data_pad_b64,omitempty"`
}

Metadata is the round-trip metadata bundle written next to the unpacked PNG/CSV artefacts. Anything that cannot be recovered byte-for-byte from the other files lives here.

type MinimapMetadata

type MinimapMetadata struct {
	Width  int `json:"width"`
	Height int `json:"height"`
}

MinimapMetadata records the minimap dimensions for round-trip.

type OptimizeOptions

type OptimizeOptions struct {
	// SimilarityPercent is the maximum mean per-channel pixel difference
	// (expressed as a percent of 255) between two tile graphics for them
	// to be treated as visually similar and consolidated.  Set to 0 to
	// skip the similarity pass and only collapse byte-identical tiles.
	SimilarityPercent float64

	// Palette converts paletted tile bytes to RGB when scoring visual
	// similarity.  Required when SimilarityPercent > 0.
	Palette color.Palette

	// KeepUnused keeps tile graphics that no cell in the tilemap
	// references.  Defaults to false (unreferenced tiles are dropped).
	KeepUnused bool

	// Progress receives one-line human-readable status updates while the
	// optimisation runs.  Pass os.Stderr for CLI use, or nil to silence.
	Progress io.Writer
}

OptimizeOptions tunes the Map.Optimize pass.

type OptimizeStats

type OptimizeStats struct {
	TilesBefore      int
	TilesAfter       int
	ExactMerges      int
	SimilarityMerges int
	UnusedRemoved    int
}

OptimizeStats summarises Map.Optimize results.

type TileAttr

type TileAttr struct {
	Height  uint8  // Elevation at this cell
	Feature uint16 // Feature index (0xFFFF = none)
	Pad     uint8
}

TileAttr is the per-cell attribute (4 bytes). There is one attribute per 16×16 pixel cell — 4 per 32×32 tile.

type UnpackOptions

type UnpackOptions struct {
	Lossless bool
}

UnpackOptions tunes what Unpack writes alongside the artefact files.

When Lossless is true the metadata.json carries the full feature name table (and its raw 128-byte buffers when they hold trailing scratch memory), so the directory can be packed back into a byte-identical TNT. When false the features field is omitted and a subsequent Pack call rebuilds the feature table from the unique names referenced in features.csv — convenient for editing but not byte-stable.

Directories

Path Synopsis
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.

Jump to

Keyboard shortcuts

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