Format reference · .tdf

Text definitions & schemas

Explore TDF syntax, watch duplicate keys resolve, compare absent and empty values, and navigate the weapon, feature and global-data schemas.

Encoding
Text, sampled ASCII / CP1252
Structure
Nested named sections
Values
Semicolon-terminated text
Lookup
ASCII case-insensitive

One grammar, many kinds of game data

TDF is the shared text syntax behind unit definitions, maps, menus, weapons, features and global tables. Its building blocks are small: named sections, nested braces, and key=value; assignments. The consumer decides what the text means.

Read the textBuild a treeCompile a family
Blank comments while preserving length.Keep sibling sections in file order; resolve keys through the insertion rule below.Apply the family’s typed reads, defaults, field widths and resource links.
A semicolon ends a value, even across newlines.Sections and keys use ASCII case-insensitive lookup.Lists and capability bits are consumer rules, not grammar.

Find the right schema

File familyTop-level recordsWhat the consumer creates
FBI · units/[UNITINFO]Unit definitions and references to models, scripts, weapons and movement classes.
GUI · guis/Ordered gadgets with nested [COMMON]Panels, controls and artwork bindings.
OTA · maps/Map/mission metadataWorld settings, mission and placement information.
gamedata/Family-specific names such as [CLASS0], [SIDE0]Movement, sides, sound categories, LOS and other global tables.
features/Feature namesMap objects, wrecks, reclaim yields and successor features.
weapons/Weapon names and nested [DAMAGE]Weapon records selected by numeric ID.
download/Ordered placement sectionsBuilder/page/button placements.
camps/useonly/Unit-name sections, normally emptyMission eligibility restrictions.

Syntax and parsing

// An original syntax example; values are text until a consumer reads them.
[Example] {
    name=Relay station;
    empty=;
    numbers=2, 4, 6;
    words=FIELD UTILITY;
    [Bounds] { x1=12; y1=24; }
}

Established — parser rules: [02 §4], [02 R-CAT-01 §3] and [02 R-MALF-01 §4] own the following syntax. Encoding and line-ending observations are limited to the sampled retail files.

  • Whitespace (spaces, tabs, newlines) between tokens is insignificant; multiple assignments may share a line.
  • Section and key lookup is ASCII case-insensitive. Retail data mixes cases freely ([UNITINFO], [GlobalHeader], canbuild1=).
  • Values run to the next ;, including any intervening newlines. There is no quoting or escape mechanism. The parser trims space, tab, CR and LF from both ends of keys, values and section names; interior whitespace remains. A missing semicolon can therefore swallow later assignments.
  • /* ... */ block comments also appear in retail data (weapons/WEAPONS.TDF: rendertype=4; /* 2D bitmap */), including inline after assignments; // comments may follow values on a line.
  • Duplicate keys are governed by “Duplicate keys” below. Duplicate sibling section names are kept as separate nodes; a first-match section accessor returns the first.
  • The sampled files use ASCII/Windows-1252; localized strings carry high-byte characters (GermanDescription=Überschwerer...). No BOM or embedded NUL was observed; those are corpus facts, not validation checks.
  • Line endings are CRLF in retail data; accept any.

Duplicate keys

Watch the key store

The same spelling can survive twice

Source order
[Example] {
  Power=3;
  power=7;
  Power=9;
}

After Power=9;: insert at lower bound.

Stored equal-key run · lookup reads row 0
IndexSpellingValue
0 → readPower9
1power7
2Power3

Case-insensitive lookup result: 9

Step through three assignments from the original TDF. New case variants insert at the head of the equal-key run. When Power returns after power, its old spelling is behind the head, so it inserts again. Case-insensitive lookup reads the latest value, 9. The checked state manifest records every intermediate vector.

Established. A section’s key store is a vector kept in case-insensitive order, and the parser builds it by insertion in source order, not by sorting. Per assignment:

  1. take the case-insensitive lower bound of the key over the vector — the first entry whose folded key is not less than the new key — the head of an equal-key run, when present;
  2. compare the new spelling byte for byte against the entry at that position only — the head of the fold-equal run, never the rest of it;
  3. equal spelling: the value is replaced in place, so the entry keeps the position the first occurrence gave it;
  4. any other outcome: the entry is inserted at the lower bound, i.e. in front of every fold-equal entry already present.

Typed accessors take the same case-insensitive lower bound and read the entry there, so the variant an accessor returns is the last one parsed. Two consequences follow from step 2’s narrow comparison:

  • repeating one spelling with nothing in between is ordinary last-write-wins, one entry;
  • but a spelling that a later case variant has pushed behind the run head is no longer what step 2 compares against, so a further assignment with that spelling is inserted rather than folded — one spelling can hold two entries.

Established — corpus example: twelve unit records in the cited 438-file sweep author MaxWaterDepth=0 followed by maxwaterdepth=255; the accessor reads 255. The aircraft water-floor consequence belongs to [04 R-AIR-01 §6a]. This is a stock-content rule, not merely a third-party compatibility edge.

Typed values and defaults

Follow the conversion

Missing, empty and zero are different inputs

01 / Source(no x assignment)
02 / Integer read, default 77
03 / Example packed bit1 · set

The key is absent, so the accessor returns its supplied default 7. The illustrative one-bit store keeps 7 & 1 = 1.

Choose an authored input. The first stage is an integer accessor with an illustrative default of 7; the second is an illustrative packed one-bit field. Actual defaults and storage widths belong to each schema. A value of 2 is nonzero, yet a low-bit flag stores zero.

Established: a default applies only when the key is absent. Present 0, empty text, or unparsable integer text produces zero rather than the caller’s nonzero default. For example, with integer default 7, absent x returns 7 while x=0; and x=junk; return 0. Numeric accessors do not return a separate found flag; string/raw accessors do. [02 §4]

Integer conversion accepts leading whitespace, an optional sign and decimal digits, ignores trailing junk, and wraps to 32 bits. Fixed-point conversion multiplies the floating value by 65,536 before truncation; its absent-key default is already in stored units. A string destination of size N keeps at most N−1 authored bytes plus a terminator. Localization first looks up <language><key>, then the plain key [02 §3].

There is no universal Boolean conversion: the accessor returns an integer and the field’s store decides its interpretation. In particular, packed unit and weapon flags keep value & 1; authored 2 clears them. Use the per-family accessor/default/width table [02 R-KEYS-01 §5] rather than treating every nonzero number as true.

Floating conversion

Established — linked-runtime scanner, initial numeric locale. Floating reads skip space, tab, newline, vertical tab, form feed and carriage return, then accept an optional sign, decimal digits with an optional decimal point, and an optional exponent introduced by e, E, d or D. The exponent may have one sign and must contain digits to take effect: 12e, 12d- and 12E+oops all produce 12. A sign without an exponent marker ends the number, so 12-3 also produces 12. Trailing bytes are ignored. A mantissa without any digits produces positive zero; Inf and NaN are not special tokens, and hexadecimal notation is not recognized (0x1p2 produces zero). The initial decimal-point character is .; a comma ends the prefix [02 §4].

Established — result boundary and range. The linked conversion first constructs a binary64 result, which the accessor returns at working precision. Caller arithmetic can retain that wider working precision, but it starts from the already-converted binary64 value. The accessor ignores conversion status: overflow produces signed infinity; underflow can produce a subnormal or signed zero. An all-zero mantissa remains signed zero even with a huge positive exponent. Failed-text zero is positive, so -junk and -0 differ in sign [02 §4].

Unknown — exact decimal rounding and locale lifetime. The conversion uses bounded significant-digit accumulation and intermediate decimal scaling before binary64 finalization. Equivalence with Nanolathe’s host converter for long mantissas, rounding boundaries, or extreme exponents offset by very long mantissas remains unverified. A bounded trace of those arithmetic helpers with authored edge cases would settle it. The initial numeric locale is established; whether retail ever selects an alternate decimal separator or extended-byte classification needs a trace of locale selection. Nanolathe retains host binary64 rounding and . as explicit placeholders for those residuals.

Schema families

gamedata/ — global tables

The movement-class, side-data and sound-category keys the executable reads are tabulated with their consumers in [02 R-KEYS-01 §5].

SIDEDATA.TDF — the two sides and the initial build tree. [SIDE0] / [SIDE1] define per-side identity and HUD layout, with nested rectangle sections; the [CANBUILD] section lists what each stock unit can build.

[SIDE0] {
    name=EXAMPLE;
    nameprefix=EXAMPLE;
    commander=EX_BUILDER;
    [LOGO] { x1=12; y1=8; x2=36; y2=32; }
}
[CANBUILD] {
    [EX_BUILDER] { canbuild1=EX_RELAY; canbuild2=EX_DEPOT; }
}

(Original abbreviated schema example; resource names are illustrative.) Established: the resolved download-menu items append after the side CANBUILD list under the documented count gate [02 R-CAT-01 §5] / [02 R-CAT-01 §8].

MOVEINFO.TDF — movement classes referenced by FBI MovementClass=:

[CLASS0] {
    Name=EX_LIGHT;
    FootprintX=2; FootprintZ=2;
    MaxWaterDepth=8; MaxSlope=16;
}

Sections are [CLASS0], [CLASS1], …; the Name value is what FBI files reference (the engine interns each section’s authored Name value into the class record head and resolves the FBI MovementClass string against those interned values, case-insensitively — confirmed by direct analysis). The loader scans CLASS0 through CLASS31, bounded by its 32-slot pool: a missing section is skipped without stopping the scan. The retail file has 15 classes and four further keys:

KeyClassesMeaning
MinWaterDepth5Minimum depth the class needs (ship classes)
MaxWaterSlope3Slope limit that applies over water, separately from MaxSlope. TANKDH3 authors 30; both hover classes author 255 alongside MaxSlope=12, which is what lets a hovercraft cross steep sea floor. Initialization: every class record is pre-filled at startup with MaxSlope = BadSlope = MaxWaterSlope = BadWaterSlope = 255 and MaxWaterDepth/MinWaterDepth = ±10000 before any parse, and the engine then runs three unconditional clamps (MaxSlope = min(MaxSlope, MaxWaterSlope), then the two Bad values clamped to their Max counterparts). An omitted MaxWaterSlope therefore leaves MaxSlope at its authored value; the pool does not start zero-filled, so an omitted class does not compile to MaxSlope = 0 [04 §6.1 R-DOC04-A].
BadSlope2Hover classes only, 12. Established: the movement classifier reads it as the clear-vs-steep boundary — slopes at or below BadSlope are clear, slopes between BadSlope and MaxSlope are the passable-but-penalized steep tier, slopes above MaxSlope are hard-blocked.
BadWaterSlope2Hover classes only, 255. Same mechanism over water.

Established: all four slope thresholds are live. The ordered reads and clamps are [04 §6.1 R-DOC04-A]; classification and the clear/steep/blocked outcomes are owned by doc 04’s movement contracts. Nanolathe compiles them into content.MovementClass and consumes them through movement.Profile.

SOUND.TDF — sound categories referenced by FBI SoundCategory=. Each section maps event slots to WAV basenames (no extension) in sounds/:

[EX_UTILITY] {
    select1=ex_select;
    ok1=ex_acknowledge;
    arrived1=ex_arrived;
    cant1=ex_unavailable;
}

The complete slot set used across retail categories: select1, ok1, arrived1, cant1, underattack, count0count5, canceldestruct, activate, deactivate, build, repair, working, cloak, uncloak, capture, unitcomplete.

ALLSOUND.TDF — global UI/game event sounds: one section per event with a single sound= key ([ActivateAllStatBars] { sound=explode.wav; }).

METEOR.TDF[Default] section with the meteor-shower parameters used when an OTA doesn’t override them (MeteorWeapon, MeteorRadius, MeteorDensity, MeteorDuration, MeteorInterval).

HELP.TDF[Help] with Line0=CTRL+A|Select all units; … lines for the F1 help screen (the | separates key from description).

TRANSLATE.TDF — one section per English string, keys are language names (German=, French=, piglatin=…).

LOS.TDF — Established: [TABLEINFO] numtables selects TABLE0 through TABLE<numtables-1>. Each table’s numlines selects line0 onward; a line stores a point count followed by that many coordinate pairs. [03 R-VIS-01 §3] owns quadrant expansion, table selection and the ray walk. The declared count, not the number of shipped sections, bounds selection.

CATEGORY.TDF — category names with description=; informational only (categories used in FBI files do not need to appear here).

BUILDINFO.TDF / UNITVIEW.TDF — build-machine metadata and Unit Viewer resources; no gameplay effect.

features/ — map features and corpses

The executable-read feature keys, with accessor, width, default and consumer, are tabulated in [02 R-KEYS-01 §5].

Feature definitions describe reclaimable/destructible map objects: trees, rocks, metal deposits, geothermal vents, and unit corpses (_dead / _heap). Files group many [featurename] sections. Original abbreviated example of a corpse feature, referenced by Corpse=ex_relay_dead;:

[ex_relay_dead] {
    description=Relay wreckage;
    object=ex_relay_dead;
    featuredead=ex_relay_heap;
    footprintx=2; footprintz=2;
    height=18; blocking=1;
    metal=40; damage=200;
    reclaimable=1;
}

Field reference (all optional unless the feature type needs them):

KeyMeaning
worldWorld types the feature suits (editor filter, e.g. All Worlds). Authored on all 1,645 retail feature records and inert — the executable has no string for it.
descriptionHover text (Wreckage)
categoryGrouping (arm_corpses, heaps, rocks, steamvents, …)
object3DO model name for 3D features (corpses, rocks)
filename + seqnameFor 2D (sprite) features: GAF file and entry name
animating / animtrans / shadtransAnimation / transparency flags for sprite features
seqnameshadShadow sprite entry. The engine also reads seqnamedieshad and seqnamereclamateshad, the shadow companions of seqnamedie and seqnamereclamate.
footprintx, footprintzSize in 16-pixel grid cells
heightHeight for shot-over tests. Also the resurrection order’s approach-phase draw bound: y = terrainHeight(cell) + boundedDraw(height) — the sole simulation-RNG draw resurrection consumes, in phase 1, not a placement effect [05 R-WORK-01 §7]. There is no separate authored “resurrection spread” or “jitter spread” key — resurrectspread, jitterspread and a bare spread are all absent from the retail feature parser’s key census and from every stock feature section (census: 177 files, 1,645 sections, 41 distinct keys, none of the three) [05 R-FEAT-01 §1]
blocking1 = blocks unit movement
hitdensityCommunity-understood as hit-probability weighting. Authored on all 1,645 retail records and inert — no string for it exists in the executable.
damageHP before turning into featuredead (or vanishing)
featuredeadFeature this becomes when destroyed
metal, energyReclaim yield; for metal deposits metal is the extraction concentration (~0–255). Read as integers, masked to 16 bits, then stored as floats; a deposit’s metal is copied (low byte) into every plot cell it covers when the deposit is also indestructible=1 [R-FEAT-01 §7]
reclaimable, autoreclaimableCan be reclaimed / is a candidate for area reclaim. autoreclaimable defaults to 1 and has no other reader [R-FEAT-01 §6]
featurereclamate, seqnamereclamateLeftover feature and animation when reclaimed
flamable, sparktime, spreadchance, burnweapon, featureburnt, seqnameburn, seqnameburnshadFire behavior for burnable features. These are the keys the engine reads. sparktime is a float in seconds, stored as trunc(sparktime × 30) ticks (int16); the ignition countdown is half + random(half) visits with half = ticks >> 1 [R-FEAT-01 §9]. A feature without seqnameburn can never ignite; object features never read the seqname* keys at all
burnmin, burnmaxAuthored on 9 files (115 records) and inert — no string for either exists in the executable, so burn duration is not authored this way.
geothermal1 = geothermal plants can build here
indestructible, nodisplayinfo, nodrawundergrayFlags. permanent is authored but inert — no such key string exists in the executable (the only Permanent string is a lobby line-of-sight label). sinktime is likewise absent; sinking is a fixed rate [R-FEAT-01 §13]
seqnamedieAnimation/feature left when destroyed
reproduce, reproduceareaGrowth mechanic, authored on 19 files (310 records). Both keys are read by the engine, so it is not unused.

TNT maps reference features by name (see tnt.md); the engine searches all loaded feature TDFs for the section, and reports a missing one with Record "%s" missing from feature files.

A whole-string census of the retail executable finds one bounded feature-key table. Alongside the keys it carries the literals REUSE, treeburn, Normal Features, Fortification, Fortification_Core, DragonsTeeth and DragonsTeeth_Core, which are feature names and category labels the engine knows by name rather than by authored data. One shipped file misspells the reclaim successor as featurereclamamate; the read spelling is featurereclamate.

weapons/ — weapon definitions

Every weapon key the executable reads, with its accessor, stored width, default and consumer section, is tabulated in [02 R-KEYS-01 §5]; the [DAMAGE] block’s construction (and the fact that a same-ID re-parse appends to the earlier record’s override table rather than replacing it) is [06 R-DMG-01 §1].

Weapon sections are referenced by name from FBI Weapon1..3= and from OTA MeteorWeapon=. Retail data spreads them over weapons/*.tdf (WEAPONS, LASERS, CANNONS, MISSILES, ROCKETS, UNITS, FIRES, METEORS…).

Historical asset commentary. gamedata/WEAPONS.TDF contains Cavedog’s field descriptions, but is not a compiled weapon-family input. Established: retail and Nanolathe compile only the selected archive winners under weapons/*.tdf; there is no gamedata fallback [02 R-CONTENT-02]. The commentary is useful authoring context, while executable-owned field contracts and the generated key table determine which keys are read and what they do.

Original abbreviated weapon-schema example (the names and ID are illustrative):

[EX_PULSE] {
    ID=42;
    name=Example pulse;
    rendertype=0;
    lineofsight=1; turret=1;
    range=160; reloadtime=.5;
    weaponvelocity=240;
    areaofeffect=8;
    [DAMAGE] { default=10; }
}

Weapons fall into three basic categories (per the retail file’s own comment): ballistic (ballistic=1, arcing under gravity), line-of-sight (lineofsight=1, straight), and dropped (dropped=1, bombs).

Which weapon keys the engine reads

A whole-string census of the retail executable finds one contiguous weapon-key table alongside the DAMAGE subsection name and default. The following entries need special care:

KeyAuthored inStatus
ID71 files, 180 recordsRead, and it selects the record slot. The weapon parser reads ID with the integer accessor and a default of −1 first; the authored value chooses which weapon record the parser fills, and the section name is then copied into that record as its catalog name (name is a separate 64-byte display string). ID is not inert, whatever the whole-string census suggests: that census misses very short strings (the same artifact that hid the textual HAPI magic), and the executable’s weapon-key table does carry ID. An implementation that resolves weapons by section name only mis-assigns records whenever authored ID values differ from file order. There are 256 nominal slots, IDs 0–255, plus the shared unreachable scratch record selected by default ID −1. Other out-of-range values are unchecked malformed selections [02 R-MALF-01 §5].
aimrate3 filesInert, despite being documented in gamedata/WEAPONS.TDF itself. Nothing in the executable can read it.
startfire1 fileInert.

weapontype2 likewise has no string and is not a retail key. Neither does movingaccuracy or noselfdamage, both of which circulate in third-party documentation.

Established: shellweapon is read even though the cited stock sample never authors it. Per-shot costs use energypershot and metalpershot; the historical comment’s bare energy and metal are not established weapon-cost aliases [02 §5].

Behavior flags use the integer accessor, default zero, with only the low bit retained. Their consumers and the stored field widths belong to [02 R-KEYS-01 §5]; the packed runtime representation is not an on-disk TDF layout. noautorange is an established parsed flag, with lifetime behavior owned by [06 §7.3].

KeyMeaning
IDNumeric weapon ID. Read by the engine and used to select the weapon record slot (default −1 = the slot before the table); the section name becomes the record’s catalog name. See the ID row of the census table above.
nameDisplay name
rendertypeProjectile rendering: 0 laser, 1 3D model, 2 not rendered, 3 dgun, 4 plasma/bitmap shell, 5 flame, 6 bomb, 7 lightning
model3DO model (rendertype 1), no extension
color, color2Palette indexes for beam/shell colors
rangeRange in map pixels
reloadtimeSeconds between shots/bursts (decimal)
burst, burstrateShots per burst and intra-burst delay
weaponvelocity, startvelocityAuthored world units/second; floating accessor, default 0, scaled by 65,536/30 before truncation to stored 16.16 units/tick [02 §5].
weaponaccelerationAuthored world units/second squared; floating accessor, default 0, scaled by 65,536/900 before truncation [02 §5].
weapontimerProjectile lifetime in seconds (0 = computed for ballistic)
durationBeam length/time for beam weapons
beamweapon1 = laser-style beam
ballistic / lineofsight / droppedCategory flags
guidance, tracks, turnrateHoming behavior; turnrate in angular units (65536 = circle)/sec
cruise, vlaunch, twophase, flighttimeVertical-launch / two-phase missiles (nukes, starbursts). weapontype2 circulates in third-party docs and is not a retail key.
accuracy, tolerance, pitchtoleranceAiming: tolerance is how far off-aim firing is allowed (angular units)
sprayangleRandom spread (angular units) for burst weapons
areaofeffectSplash diameter in pixels
edgeeffectivenessDamage fraction at splash edge (0–1)
energypershot, metalpershotEstablished: firing costs [02 §5]. The historical asset commentary mentions bare energy/metal; Nanolathe does not treat them as aliases for these parsed fields.
commandfireRequires explicit user fire order (D-gun, nukes)
toairweaponWeapon only engages air targets (anti-air missiles; retail key, undocumented historically)
holdtimeFollow-camera hold, in whole simulation ticks (authored in seconds, multiplied by 30 and truncated with the other time-valued weapon keys). When the projectile the camera is following retires, the camera freezes on that projectile’s last point and stays there for holdtime ticks before resuming ordinary following. It has no projectile-motion effect at all: every reader is a projectile-retirement path that loads the camera hold counter, not motion code. Established; the engine side is [06 §7.3], the camera side [07 §10].
turretWeapon must be deployed from a mount with 360° rotation and pitch (143 retail weapons)
coverage“What the protection umbrella is for weapons that shoot other weapons” — the interceptor’s protected radius
minbarrelangleLowest angle in degrees the barrels can point, used in the ballistic solution
aimrateDocumented in gamedata/WEAPONS.TDF as average aiming speed in 64K degrees per second, and inert — the executable has no string for it.
propellerThe weapon’s model has a propeller that spins
startfireAuthored once in the retail corpus, and inert.
stockpile, targetableStockpiled weapon; can be intercepted
interceptorRetail interceptor weapon selector; the runtime reserves one legal stockpiled slot per targetable projectile
paralyzerStuns instead of damages (damage value = stun ticks)
noautorange, noexplode, burnblow, groundbounce, selfprop, waterweapon, unitsonly, noradarProjectile behavior flags
firestarter% chance to ignite flammable features
smoketrail, smokedelay, startsmoke, endsmokeSmoke visuals
soundstart, soundhit, soundwater, soundtriggerWAV basenames; soundtrigger=1 plays per burst shot
explosiongaf/explosionart (+ water…, lava… variants)Impact animation: GAF file basename + entry name
shakemagnitude, shakedurationScreen shake
randomdecayFloating seconds, default 0, multiplied by 30 and truncated into a 16-bit tick value. The centered burst-clone expiry jitter is established in [06 §4.3].
meteorMarks the meteor weapon
[DAMAGE]Subsection: default= damage per hit, plus per-unit-name overrides (corkrog=2460;)

Established: render fields remain compiled data. Projectile sprite selection and frame progression belong to [03 §5.4] and [06 R-WFX-01 §1]; impact smoke belongs to [03 §5.5]. This format document does not prescribe a second EMG frame-selection or impact-smoke rule. The older WEAPON-VISUAL-EMG-CANNONSHELL-001 and WEAPON-VISUAL-IMPACT-SMOKE-001 labels are historical references, superseded by those owning contracts.

download/ — build-menu placement

Add-on units (and expansion units) attach themselves to construction menus with download/<unit>.tdf. Original placement example:

[FirstPlacement] {
    UNITMENU=EX_BUILDER;
    MENU=2;
    BUTTON=0;
    UNITNAME=EX_RELAY;
}

Established: sections are visited in file order; their names are not parsed as ordinals. Stock authors [MENUENTRY1], [MENUENTRY2], … one per builder/menu/button placement. MENU and BUTTON use the integer accessor and retain the low byte; UNITMENU and UNITNAME use 32-byte string reads [02 R-CAT-01 §8]. The first visible build page is MENU=2 (page numbering is off-by-one); two units claiming the same builder/page/button conflict — only one appears.

camps/useonly/ — mission unit restrictions

A mission’s OTA can set useonlyunits=<name>.tdf; referring to camps/useonly/<name>.tdf, which whitelists buildable units as empty sections:

[EX_BUILDER] { }
[EX_RELAY] { }

Established: mission restriction loading clears eligibility and re-enables the listed unit names before the battle-entry catalog compaction. This changes the available definitions, not merely button color [08 R-ENTRY-01 §2].

Other TDF-syntax files

  • maps/*.otaota.md
  • units/*.fbifbi.md
  • guis/*.guigui.md
  • maps/MULTIPLAY.TDF, campaign definitions under camps/ — mission sequencing (tdfcamp schema: campaign entries pointing at OTA missions, briefing text/sounds).
  • ai/*.txt — AI profiles referenced by OTA aiprofile=; plain text, not TDF.

Malformed text and loader outcomes

Owned by [02 §4] and [02 R-MALF-01 §4]; the byte-level facts:

  • Comments are blanked to spaces first, length preserved: // to the end of the line (newline kept), /* … */ inclusive, an unterminated /* to the end of the text.
  • The parser then walks the text: [ needs a ] somewhere after it and, after whitespace, a {; } closes the section (a } at the top level ends the parse and the rest of the file is ignored); anything else starts a key = value ; field whose = and ; are found by scanning forward to the end of the text.
  • Every failure — Data field - '=' not found, Data field - ';' not found, Sub-record - closing ']' not found, Sub-record - opening '{' not found, End of file - nextblock not zero (end of text inside an open section) — is shown as Parse error in .TDF File! <diagnostic> - name = '<section>' from file <path> in a system-modal box and the process exits with code 1. There is no recovery and no partial tree.
  • A missing file, or one of zero length, is the only recoverable failure: the loader returns no tree and typed reads return their defaults.
  • Numeric text: the integer accessor wraps modulo 2³² (no overflow test); the fixed-point accessor multiplies by 65,536, truncates to signed 64 bits, and retains the low 32 bits. Finite signed-32 overflow wraps; non-finite or signed-64 overflow produces a zero low word [01 R-DET-01 §1] [02 R-MALF-01 §4, RT-01]. The floating accessor is the C-runtime decimal conversion.

Unknowns and caveats

  • Established: the grammar is traced, not merely inferred from corpus style. } closes a section; it does not consume a following ;. Do not prescribe }; as an optional terminator. Comments are blanked before parsing even inside would-be values. Duplicate sections remain in source order; duplicate-key lookup follows the insertion rule above.

  • Established: randomdecay and the scalar conversions are documented in [02 §5] / [06 §4.3]. Historical weapon commentary is authoring context, not proof a key is live or its prose matches the traced consumer.

  • MOVEINFO.TDF’s BadSlope/BadWaterSlope pair is authored only by the two hover classes; both keys are read by the engine along with MaxWaterSlope, as the clear-vs-steep boundary of the movement classifier (see the table above). The third-party controller keys pivotturn, reverse, arcturn, minturnradius and minturnspeed have no strings in the executable.

  • Retail authors featurereclamamate (a typo for featurereclamate) ten times in features/acid/acidplants.tdf. Established: retail and Nanolathe read only featurereclamate; the typo is retained as inert source data and those records receive no reclaim successor from it [02 §5].

  • Established: side CANBUILD lists are populated first, then resolved download-menu items append under the count gate [02 R-CAT-01 §5] [02 R-CAT-01 §8].

  • Established: LOS.TDF declares numtables=9 while containing 12 table sections; only the declared range is selected [03 R-VIS-01 §3].

  • Unknown: exact long-mantissa floating rounding, extreme exponent cancellation and alternate numeric-locale selection remain open under “Floating conversion” above. Exponent spelling, ordinary malformed-prefix handling, overflow result selection and signed-zero behavior are established; ordinary host tests do not prove the remaining arithmetic edges.

  • Established — evidence limit: a literal-string census alone cannot prove a key unreadable. Language-prefixed keys and numbered keys are built dynamically, and a minimum-length string export can omit short keys such as ID. Negative key claims require the relevant parser/caller census.

Nanolathe implementation and safety policy

Established — implementation inspection: formats/tdf.go preserves source items and builds a separate resolved key view; it implements comment blanking, trimming, multiline values, first-match sibling lookup and the duplicate-key rule. It additionally accepts an adjacent semicolon after }; that host extension is not part of the traced retail grammar. formats/tdf_typed.go applies absent-only defaults and the established floating-prefix/range rules; formats/tdf_typed_test.go locks alternate exponents, incomplete exponents, ASCII whitespace, overflow and signed zero. Its BoolValue helper tests nonzero, so packed catalog flags instead use their field-specific low-bit stores. Parse size/depth limits and returned Go errors are host safety policy; retail’s malformed-input path is fatal. Source-item preservation does not mean byte-for-byte preservation of comments and outer whitespace.

Historical sources

  • gamedata/WEAPONS.TDF (from totala1.hpi) — Cavedog’s own comment header supplies historical authoring descriptions. Executable-owned contracts [02 §5] / [02 R-KEYS-01 §5] determine live keys, defaults and conversions when that commentary disagrees.
  • TA Design Guide pages, at https://units.tauniverse.com/tutorials/tadesign/tadesign/<page> (also mirrored under https://files.tauniverse.com/files/ta/resources/tutorials/ta-design-guide/browse-online/tadesign/): Gamedata TDF Information (tdfgdata.htm), Features TDF Information (tdffeat.htm), Weapon TDF Information (tdfweapon.htm), Download TDF Information (tdfdown.htm), Useonly TDF Information (tdfuonly.htm), Campaign TDF Information (tdfcamp.htm).
  • Examples verified against gamedata/SIDEDATA.TDF, gamedata/MOVEINFO.TDF, gamedata/SOUND.TDF, features/corpses/arm_corpses.tdf, weapons/WEAPONS.TDF from totala1.hpi and download/ARMAMB.TDF from CCDATA.CCX.
  • Nanolathe implementation: formats/tdf.go and formats/tdf_typed.go parse text; internal/content/compile_*.go compiles the family records. Behavioral precedence remains with research/retail-executable-spec.

Pinned research and implementation

This page adapts the owning TDF research at commit 72dcc02. The complete source snapshot preserves the original research, evidence IDs and confidence labels. All illustrative assets on this page are original authored examples; none are extracted retail assets.

Owning referenceScope
02 · Content, VFS, formats and data loadingSyntax, typed accessors, resource loading and malformed-input behavior.
04 · Units, orders, scripts and movementVM operations, unit data and runtime consumers.
07 · Interface, input, camera and front endGadget parsing, building, events, focus and rendering.
03 · Retail executable contract: world presentation, visibility, audio, and videoOwning runtime contracts for the corresponding numbered evidence cited above.
05 · Retail engine economy, construction, players, and featuresOwning runtime contracts for the corresponding numbered evidence cited above.
06 · Retail executable specification: weapons, projectiles, damage, and effectsOwning runtime contracts for the corresponding numbered evidence cited above.
08 · Retail engine sessions, campaign, AI, networking, save, and replayOwning runtime contracts for the corresponding numbered evidence cited above.

Evidence labels distinguish established behavior, bounded observations, supported inferences, unknowns and Nanolathe host policy. A field appearing in an authored file does not by itself establish a runtime consumer.