LTR File Format - OpenKotOR/PyKotor GitHub Wiki
LTR — Letter Probability Tables
LTR (Letter) files store the probability tables the engine uses to procedurally generate NPC names. The data is a third-order Markov chain: given zero, one, or two characters of context, the tables encode the likelihood of each possible next character appearing at the start, middle, or end of a name. This lets the engine produce random names that sound plausible for a given species or culture without hardcoding a name list.
KotOR's LTR files use a 28-character alphabet (a–z plus ' and -), which is a KotOR-specific extension of the 26-character alphabet used in Neverwinter Nights [ltr_data.py L57–60 — "NWN uses 26-character set"]. The alphabet size is stored in the file header, so readers can handle either variant. Like all resources, LTR files are resolved through the standard resource resolution order (override -> MOD/ERF/SAV -> KEY/BIF).
Table of Contents
- LTR — Letter Probability Tables
File Structure Overview
- KotOR always uses the 28-character alphabet (
a–zplus'and-). Neverwinter Nights (NWN) used 26 characters; the header stores the count. This is a KotOR-specific difference from NWN. - LTR files are binary: a short header (see below) followed by three probability tables (singles, doubles, triples) stored as contiguous float32 arrays (cumulative probabilities in vanilla data).
Cross-reference implementations (line anchors are against master and may drift):
-
PyKotor:
- on-disk layout and offsets in module docstring:
ltr_data.pyL1–L50 LTRmodel +generate():ltr_data.pyL64–L288- binary I/O:
LTRBinaryReader.loadL55–L113 LTRBinaryWriter.writeL125–L156
- on-disk layout and offsets in module docstring:
-
ltrreader.cppLtrReader::loadL27–L61 (8-byte"LTR V1.0"signature,uint8letter count, nestedreadLetterSetL63–L79)- struct layout
include/reone/resource/ltr.hL24–L48
-
LTRObject.tsreadBufferL51–L124 (LTR_HEADER_LENGTH = 9)- runtime name roll
getNameL128–L210
-
Kotor.NET — check
Kotor.NET/Formats/for LTR support in your checkout (paths have changed across branches; no stable permalink verified from this wiki revision).
Binary Format
Header
The header is 9 bytes for standard Aurora/KotOR LTR (not 12): 4-byte type, 4-byte version, 1-byte letter count. PyKotor and reone both read 8 bytes of signature as the literal string LTR V1.0 (no separate padding), then the letter count.
| Name | type | offset | size | Description |
|---|---|---|---|---|
| File Type + Version | char | 0 (0x00) | 8 | ASCII "LTR V1.0" (see io_ltr.py L66–L76reone ltrreader.cpp L28). |
| Letter Count | uint8 | 8 (0x08) | 1 | Must be 28 for KotOR (PyKotor enforces this: io_ltr.py L81–L84). |
Single-Letter Block
Immediately after the header, the single-letter probabilities are stored as three arrays of letter_count floats (start, middle, end). For KotOR (letter_count == 28) that is 28 × 3 × 4 = 336 bytes.
| Section | Entries | Description |
|---|---|---|
| Start | 28 floats | Probability of each letter starting a name |
| Middle | 28 floats | Probability of each letter appearing mid-name |
| End | 28 floats | Probability of each letter ending a name |
Double-Letter Blocks
For each character in the alphabet there is a double-letter block (context length 1). Each block repeats the same start/middle/end layout (28 floats each).
Total size (KotOR): 28 × 3 × 28 × 4 = 9,408 bytes.
Triple-Letter Blocks
The triple-letter section encodes 2-character context. There are letter_count × letter_count blocks, each with start/middle/end arrays of 28 floats.
Total size (KotOR): 28 × 28 × 3 × 28 × 4 = 263,424 bytes.
Layout offsets for 28-letter float32 tables are straightforward: singles begin at byte 9 immediately after the header, doubles begin at 9 + 28×3×4 = 345 (0x159), and triples begin at 345 + 28×28×3×4 = 9,753 (0x2619). Some older notes and comments used different hex offsets, so the byte counts and reader loops in io_ltr.py L88–L108 are the safer authority.
Probability Blocks
Each block is represented by the LTRBlock class in PyKotor (ltr_data.py LTRBlock L363+), mirroring LetterSet / Ltr::LetterSet in reone and xoreos. Blocks store cumulative probabilities (monotonically increasing floats) that are compared against random roll values [LTRBlock docstring — ltr_data.py L298].
- Singles (
_singles): No context; used for the very first character. - Doubles (
_doubles): Indexed by the previous character; used for the second character. - Triples (
_triples): Two-dimensional array indexed by the previous two characters; used for every character after the second.
The corresponding structures are also visible in reone ltr.h L24–L48 and xoreos ltrfile.h L57–L76.
Name Generation Process
The runtime algorithm (PyKotor, reone, xoreos, KotOR.js, etc.) follows the same broad steps:
- Seed/Random Setup – optional deterministic seed for reproducible results (PyKotor:
LTR.generateL170–L288). - First Character – roll against single-letter start weights.
- Second Character – roll against double-letter start for the previous letter.
- Third Character – roll against triple-letter start for the previous two letters.
- Subsequent Characters – roll against triple-letter middle; termination uses triple-letter end plus length heuristics (compare KotOR.js
getNameL173–L200 with PyKotorgenerateL252–L285). - Post-processing – capitalize / minimum length; retries on failed rolls.
This generation pipeline matches the older NWN-lineage C reference (mtijanic nwnltr.c), PyKotor's reader and generator (io_ltr.py L55-L156, ltr_data.py generate L170-L288), reone's LTR parser (ltrreader.cpp L27-L79), xoreos's Aurora implementation (ltrfile.cpp), and KotOR.js's LTR object logic (LTRObject.ts L51-L210).
Because PyKotor matches the binary layout described above, LTR resources round-trip with the other cited implementations for 28-letter KotOR tables.
See also
- Resource formats and resolution — Resource type identifiers and format discovery
- Bioware Aurora core formats — Aurora engine specification (includes LTR)
- 2DA File Format — Configuration tables that reference generated names
- NSS scripting reference — NWScript functions that call LTR-based name generation