UTP Format (Placeable Blueprint)
Description: The Placeable (.utp) blueprint dictates the configuration of universally interactive scenery and containers within a map. Ranging from simple locked footlockers to rigged command consoles and explodable starship barricades, .utp structs blend physical static properties (like structural HP and lock difficulties) with heavy dynamic script bindings.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .utp |
| Magic Signature | UTP / V3.2 |
| Type | Placeable Blueprint |
| Rust Reference | View rakata_generics::Utp in Rustdocs |
Data Model Structure
Rakata maps the Placeable definition directly into the rakata_generics::Utp 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 Placeable breaks down into five main categories:
- Core Identity & Geometry: The configuration for what the placeable looks like, its faction, and the text displayed when targeted (e.g.,
Appearance,TemplateResRef,LocName). - Interactive State & Dialogue: Flags determining if the placeable can be clicked, if it starts a conversation/computer sequence, or if it acts as a loot container (e.g.,
Useable,Conversation,HasInventory). - Lock & Trap Mechanics: The parameters defining whether it’s locked, what key is needed, and rules for any attached traps (e.g.,
Locked,KeyName,TrapType,DisarmDC). - Health & Destruction: The physical integrity of the object, defining if it can be destroyed and its defensive thresholds (e.g.,
HP,Hardness,Static,Plot). - Behavioral Hooks (
Scripts): The scripts that run when a player explores, attacks, or opens the placeable (e.g.,OnOpen,OnInvDisturbed,OnDamaged).
- State Validation:
rakata-lintchecks the data against engine constraints to prevent runtime bugs.
Engine Audits & Decompilation
(Decompilation logic for this section was entirely audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CSWSPlaceable::LoadPlaceable at 0x00585670.)
Because Placeables act as physical junctions for event hooking, they expose a massive suite of script triggers natively.
Structural Load Phasing
| Function | Size | Behavior |
|---|---|---|
LoadPlaceable | 5092 B | The primary physical parser evaluating 46 core metrics including health, conversation dialogues, basic trap bindings, and physical alignment states. |
ReadScriptsFromGff | – | Attaches 16 dedicated script hooks dictating behavior when the placeable is bashed, opened, unlocked, or triggered. |
Core Structural Findings
| Engine Rule | Runtime Behavior |
|---|---|
| Appearance Truncation | The engine reads Appearance as a 32-bit integer but forcefully truncates it to a single byte. Any ID above 255 automatically wraps to 0 and physically breaks the placeable model rendering. |
| Static vs. Plot Chaining | Just like Doors, if a Placeable is marked Static=1, the engine completely overrides all other behaviors and acts as if Plot=1 is true, making the placeable totally indestructible even if it has an HP value defined. |
| Default Usability Check | If the Static toggle is completely missing from the binary file, the engine automatically derives it by actively checking if the Placeable is marked as usable (!Useable). |
| Portrait Shadowing | If PortraitId is < 0xFFFE, the engine completely ignores the Portrait string ResRef and relies entirely on the ID. Any value in the Portrait ResRef field is treated as dead data. |
| Ground Pile Forcing | The engine reads whatever value you place in GroundPile, but physically overwrites it and forces it to 1 in memory, making native static configuration of this field utterly pointless. |
| Missing Door Hooks | Toolsets erroneously expose OnFailToOpen for Placeables, but the engine specifically treats this as a Door-exclusive (.utd) script hook and completely ignores it here. |
| Trap Hook Fallback | If a trap bounds check fails or the OnTrapTriggered script is left blank, the engine automatically attempts to read the traps.2da table and pulls the default script based on the specific TrapType. |
Legacy & Ignored Data
| Finding Type | Explanation |
|---|---|
| Legacy Engine Artifacts | Placeable binaries are littered with legacy metrics from older tools or other Odyssey games (Comment, OpenLockDiff, Interruptable, Type, PaletteID). The physical KOTOR engine constructor entirely ignores these. |
Implemented Linter Rules (Rakata-Lint)
Phase 1 (intra-resource, no context)
Implemented under rakata_lint::rules::utp.
- UTP-001 (Plot Chaining Context): Warns when
Static=truebutPlot=false; the engine forces Plot to true at runtime. - UTP-002 (Ghost Value Detection): Informs when
GroundPile=falsesince the engine immediately overwrites this to true on load. - UTP-003 (Dead Hook Pruning): Flags
OnFailToOpeninstances because placeables ignore this event hook (it is door-exclusive). - UTP-004 (HP Health Ceiling): Errors when
CurrentHP > HP; the engine clamps toHPon template load. - UTP-005 (Portrait Shadowing): Warns when
PortraitId < 0xFFFEandPortraitresref is set; the resref is ignored at runtime.
Phase 2 (range / 2DA / resref existence, requires LintContext)
Implemented under rakata_lint::rules::utp_range.
- UTP-006 (Appearance Bounds): Errors when
Appearancedoes not resolve to a row inplaceables.2da; engine renders missing model. - UTP-007 (Portrait Bounds): Errors when
PortraitId(when not the0xFFFE“use string Portrait” sentinel) does not resolve to a row inportraits.2da. - UTP-008 (Resref Existence): Warns when
Conversation(.dlg),Portrait(.tga), any of the 16On*script hooks (.ncs), orItemList[i].InventoryRes(.uti) does not resolve in the configured resource sources.OnFailToOpenis intentionally NOT included – UTP-003 already flags it as door-exclusive dead data.
Pending
- Appearance Truncation: Warns when
Appearanceexceeds 255 (engine truncates to a single byte before lookup, distinct from the row-count check in UTP-006). - Animation Conditional Limits: Verifies that custom
AnimationStateindices are strictly guarded byOpen==0closures.