Documentation
¶
Overview ¶
Package tnt implements reading of Total Annihilation TNT map files.
Index ¶
- Constants
- func Pack(dir string) (*Map, []Feature, error)
- func Unpack(m *Map, features []Feature, palette color.Palette, dir string) error
- func UnpackWithOptions(m *Map, features []Feature, palette color.Palette, dir string, ...) error
- func WritePNG(w io.Writer, img image.Image) error
- type Feature
- type FeatureMarker
- type FeaturePlacement
- type Header
- type HeaderMetadata
- type LintDiagnostic
- type LintOptions
- type LintSeverity
- type Map
- func (m *Map) FeatureCounts() map[int]int
- func (m *Map) GetFeaturePlacements() []FeaturePlacement
- func (m *Map) Lint(opts LintOptions) ([]LintDiagnostic, error)
- func (m *Map) LoadFeatures(r io.ReadSeeker) ([]Feature, error)
- func (m *Map) MinimapContentBounds() (contentW, contentH int)
- func (m *Map) Optimize(opts OptimizeOptions) (OptimizeStats, error)
- func (m *Map) RenderASCII(maxCols int) string
- func (m *Map) RenderBuildMap(seaLevel uint32) *image.RGBA
- func (m *Map) RenderHeightMap() *image.Gray
- func (m *Map) RenderHeightMapRaw() *image.Gray
- func (m *Map) RenderMinimap(palette color.Palette) *image.RGBA
- func (m *Map) RenderMinimapPaletted(palette color.Palette) *image.Paletted
- func (m *Map) RenderTAKHeightmap() *image.Gray
- func (m *Map) RenderTAKTerrain(tex func(name uint32) image.Image) *image.RGBA
- func (m *Map) RenderTileMap(palette color.Palette) *image.RGBA
- func (m *Map) RenderTilePaletted(index int, palette color.Palette) *image.Paletted
- func (m *Map) RenderVoidMap() *image.RGBA
- func (m *Map) Save(w io.Writer, features []Feature) error
- func (m *Map) SaveTAK(w io.Writer) error
- func (m *Map) TAKFeaturePlacements() []FeaturePlacement
- func (m *Map) TAKPixelH() int
- func (m *Map) TAKPixelW() int
- type Metadata
- type MinimapMetadata
- type OptimizeOptions
- type OptimizeStats
- type TileAttr
- type UnpackOptions
Constants ¶
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.
const HeaderSize = 64
HeaderSize is the on-disk size of the TNT header in bytes.
const MinimapVoidByte = 0x64
MinimapVoidByte is the palette index used for minimap padding (outside map area).
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.
const TileAnimEntrySize = 132
TileAnimEntrySize is the on-disk size of a tile animation/feature entry.
const TileGfxSize = 32 * 32
TileGfxSize is the byte size of a single 32×32 tile graphic.
Variables ¶
This section is empty.
Functions ¶
func Pack ¶
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 ¶
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.
Types ¶
type Feature ¶
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 ¶
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 ¶
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 ¶
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 ¶
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:
- byte-identical tile graphics are merged into a single index;
- visually-similar tile graphics whose placements share the same heightmap footprint are merged (skipped when SimilarityPercent is 0);
- 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
RenderMinimap renders the minimap as an RGBA image. Void pixels (palette index 0x64) are rendered as transparent.
func (*Map) RenderMinimapPaletted ¶
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 ¶
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 ¶
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 ¶
RenderTileMap renders the full map as an RGBA image.
func (*Map) RenderTilePaletted ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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.