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 (az 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


File Structure Overview

  • KotOR always uses the 28-character alphabet (a–z plus ' 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):


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:

  1. Seed/Random Setup – optional deterministic seed for reproducible results (PyKotor: LTR.generate L170–L288).
  2. First Character – roll against single-letter start weights.
  3. Second Character – roll against double-letter start for the previous letter.
  4. Third Character – roll against triple-letter start for the previous two letters.
  5. Subsequent Characters – roll against triple-letter middle; termination uses triple-letter end plus length heuristics (compare KotOR.js getName L173–L200 with PyKotor generate L252–L285).
  6. 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