Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UTC Format (Creature Blueprint)

Description: The Creature (.utc) blueprint format defines the attributes, stats, and behavior of all in-scene NPCs and monsters. It covers a creature’s identity, class/level, appearance, equipment, and event scripts. Because they hold so much state, Creatures are one of the most dynamic and memory-heavy templates processed by the Odyssey Engine.

At a Glance

PropertyValue
Extension(s).utc
Magic SignatureUTC / V3.2
TypeCreature Blueprint
Rust ReferenceView rakata_generics::Utc in Rustdocs

Data Model Structure

Rakata maps the Creature definition directly into the rakata_generics::Utc struct. To view the exhaustive binary schema and strict GFF field mappings, please refer to the Rustdocs for this struct, where each field is explicitly documented.

A Creature breaks down into six main categories:

  1. Core Statistics: The basic stats that define the creature’s physical capabilities (e.g., Strength, Dexterity, base HitPoints).
  2. Identity & Graphics: Identifiers that define who the creature is and what 3D model they use (e.g., Tag, Appearance_Type, Conversation).
  3. Class & Skill Progression: The mechanics that define their level, classes, and skills (e.g., ClassList, SkillList).
  4. Combat Capabilities: The specific feats and Force powers the creature can use (e.g., FeatList, SpellList).
  5. Inventory & Equipment: The exact items the creature spawns with, including both equipped gear and inventory drops (e.g., Equip_ItemList, ItemList).
  6. Event Hooks (Scripts): The behavior scripts that run when the creature reacts to the world, such as taking damage or noticing an enemy (e.g., OnNotice, OnDamaged).
  • State Validation: rakata-lint checks the data against engine constraints to prevent fatal runtime crashes.

Engine Audits & Decompilation

The following documents the engine’s exact load sequence and field requirements for .utc files mapped from swkotor.exe.

(Decompilation logic for this section was entirely audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CSWSCreatureStats::ReadStatsFromGff at 0x005afce0.)

Structural Load Phasing

FunctionSizeBehavior
ReadStatsFromGff7835 BThe massive initial pass that parses 57 basic creature scalars including strength, dexterity, and physical appearance.
LoadCreatureSets up how the creature physically sits in the world, handling their stealth states, collision size, and idle animations.
ReadScriptsFromGffAttaches all the custom event scripts that fire when the creature notices an enemy, takes damage, dies, or simply stands around (heartbeat).
ReadItemsFromGffPulls all loot into memory, structuring items specifically into equipped slots, the backpack, or dropping them entirely if a creature spawns dead.
ReadSpellsFromGffSpecifically extracts the list of any Force powers or combat feats the creature is allowed to use.

Note

Zeroed Data Elements Legacy structures referencing Tail and Wings are explicitly hardcoded to 0 during parsing and completely bypassed by the binary loader.

Core Structural Findings

The engine strictly validates parameters when loading a .utc file. Improper formatting will trigger some of KOTOR’s most notorious game crashes.

Warning

Understanding Fatal Crash Codes (0x5fX) When the game engine parses a file and hits an invalid stat, it completely aborts loading. Instead of recovering gracefully, the engine deliberately triggers a fatal crash to your desktop and returns a specific hexadecimal error code (e.g., 0x5f7 or 0x5f4). The rules below track the specific scenarios where the game will crash.

Engine RuleRuntime Behavior
Class LimitsThe engine expects a strict limit of 2 discrete class types. Providing duplicate class configuration completely crashes the game (Engine Error 0x5f7).
Race BoundsThe engine compares Race against the compiled row count of racialtypes.2da. Exceeding this boundary fatally crashes the map loader (Engine Error 0x5f4).
Saves CalculationPre-computed saving throws (SaveWill, SaveFortitude) in the .utc file are completely ignored dead data. The engine overrides them exclusively by reading willbonus and fortbonus.
Perception FaultsA non-PC PerceptionRange initiates a read against appearance.2da for PERCEPTIONDIST. Failing to resolve this distance fails the entire creature load (Engine Error 0x5f5).
Movement FallbacksIf a unique MovementRate isn’t declared, the engine logic falls back directly to default WalkRate parameters.
Hard ClampingThe engine strictly limits specific numeric bounds upon load: Gender is clamped structurally at a maximum of 4, and GoodEvil is fiercely clamped so that it cannot exceed 100.
Appearance ShiftingIf Appearance_Head is 0, the engine overrides it to 1 to prevent rendering bugs.

Legacy & Ignored Data

Finding TypeExplanation
Legacy Engine ArtifactsA staggering 17 .utc fields (such as Morale, SaveWill, BlindSpot, PaletteID) present in older files are actually Neverwinter Nights or KOTOR 2 superset metrics that the K1 engine natively ignores.

Vanilla Data Anomalies

Corpus surveys of the K1 GOG .utc set surface two anomalies in the SpecAbilityList field. Both are vanilla data quirks rather than decoder bugs; the structural reader represents them faithfully.

Stacked SpecAbilityList entries on the Bastila variants

Six Bastila .utc templates (bastila00c, p_bastilla, p_bastilla001, p_bastilla003, p_bastilla005, p_bastilla006) each carry 99 identical entries of Spell = 52 (SPECIAL_ABILITY_BODY_FUEL) in their SpecAbilityList. The engine’s loader (the SpecAbilityList block of CSWSCreatureStats::ReadStatsFromGff at 0x005afce0) walks each list element and unconditionally appends (Spell, SpellFlags, SpellCasterLevel) to the in-memory special_abilities_ array; there is no deduplication step. Each of the 99 entries occupies its own array slot with independent SpellFlags and SpellCasterLevel, so the stacking is faithfully preserved at runtime.

The loop iteration count itself is taken from CResGFF::GetListCount cast to a single byte (uVar17 & 0xff), so any UTC with more than 255 SpecAbilityList entries would have its tail silently truncated at load. Bastila’s 99 sits comfortably below that cap.

Out-of-range Spell id on the partymember template

The partymember.utc template references Spell = 299. Vanilla K1 spells.2da has 132 rows (0131), so 299 does not resolve to any row. The SpecAbilityList loader does not validate Spell against spells.2da at load time; the value is stored verbatim in the in-memory entry. spells.2da is itself read into a per-row struct array sized exactly to row_count (CSWClass::LoadSpellsTable at 0x005be4c0), so a use-time lookup of Spell = 299 indexes past the end of that array. The realised behaviour depends on heap layout at runtime and is not deterministic from the load path alone.

Both anomalies are candidate targets for future Phase 2 / Phase 3 UTC lint rules (e.g., “SpecAbilityList[].Spell must resolve to a row in spells.2da”; optionally “warn on stacked-duplicate SpecAbilityList entries unless explicitly whitelisted as a known vanilla pattern”).


Implemented Linter Rules (Rakata-Lint)

Phase 1 (intra-resource, no context)

Implemented under rakata_lint::rules::utc.

  1. UTC-001 (Appearance Correction): Warns when Appearance_Head == 0; the engine forces this to 1 at runtime.
  2. UTC-002 (Class Limit): Warns when more than 2 entries appear in ClassList; the engine ignores classes beyond the second.
  3. UTC-003 (Class Duplications): Errors when duplicate class IDs exist in ClassList; causes a fatal engine crash (0x5f7) on load.
  4. UTC-004 (Dead Save Fields): Informs when SaveWill or SaveFortitude are populated; the engine reads willbonus/fortbonus instead.
  5. UTC-005 (Gender Clamp): Warns when Gender > 4; the engine clamps to a maximum of 4.
  6. UTC-006 (GoodEvil Clamp): Warns when GoodEvil > 100; the engine clamps to a maximum of 100.
  7. UTC-007 (Toolset / Legacy Fields): Informs when any of Comment, Morale*, PaletteID, BodyVariation, TextureVar, BlindSpot, MultiplierSet, NoPermDeath, IgnoreCrePath, Hologram, WillNotRender, or LawfulChaotic are set; never read by the K1 engine.

Phase 2 (range / 2DA / resref existence, requires LintContext)

Implemented under rakata_lint::rules::utc_range.

  1. UTC-008 (Race Bounds): Errors when Race does not resolve to a row in racialtypes.2da; engine crash 0x5f4 on load.
  2. UTC-009 (Class Bounds): Errors when any ClassList[].Class does not resolve to a row in classes.2da (or is negative); engine load failure.
  3. UTC-010 (Appearance Bounds): Errors when Appearance does not resolve to a row in appearance.2da; engine renders missing model.
  4. UTC-011 (Portrait Bounds): Errors when PortraitId (when not the 0xFFFE “use string Portrait” sentinel) does not resolve to a row in portraits.2da.
  5. UTC-012 (Resref Existence): Warns when Conversation (.dlg), Portrait (.tga), any of the 14 Script* hooks (.ncs), Equip_ItemList[i].EquippedRes (.uti), or ItemList[i].InventoryRes (.uti) does not resolve in the configured resource sources.