UTS Format (Sound Object Blueprint)
A .uts file is a sound emitter: one or more .wav clips, the distance they carry, and how the engine varies them each time they play. They cover environmental hums, crowd chatter and localized looping effects.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .uts |
| Magic Signature | UTS / V3.2 |
| Type | Sound Object Blueprint |
| Rust Reference | View rakata_generics::Uts in Rustdocs |
Field Schema
The format’s field families, as an orientation before the full list.
| Category | Covers | Representative fields |
|---|---|---|
| Audio Emitters | The .wav clips the engine sequences or shuffles through | Sounds |
| Spatial Geometry | The distance boundaries that decide where the sound is audible | MinDistance, MaxDistance |
| Playback Automation | How the sound loops and strings together | Continuous, Random, Active, Looping |
| Algorithmic Variation | Runtime distortion of pitch and volume | PitchVariation, FixedVariance, VolumeVrtn |
| Procedural Generators | Marks the sound as engine-generated ambiance such as crowd chatter or combat noise | GeneratedType |
Engine Audits & Decompilation
Read from CSWSSoundObject::Load at 0x005c9040 in swkotor.exe. Provenance: derived, not attested. The rows below have not been separately re-derived, so they sit on the reverse-engineering queue.
A sound object has no script hooks. Everything it does comes from playback scalars and the randomised placement the Positional and Random* fields drive.
The load path
| Function | Size | Behaviour |
|---|---|---|
Load | 1345 B | The main parser. It reads the emitter’s placement, its distance bounds, and the volume, pitch and looping scalars. |
Sounds List | n/a | Walks the list of clips, loading each Sound resref in turn. |
Rules the engine enforces
| Engine Rule | Runtime Behaviour |
|---|---|
| Generated Type Truncation | GeneratedType is read as a 32-bit integer and stored as its low byte alone. A value above 255 therefore arrives as a different generator type than the one written. |
| Spatial Loading Context | Loaded through the area’s static map (CSWSArea::LoadSounds), the engine skips the .uts coordinates and takes XPosition/YPosition/ZPosition from the .git. A sound carries no orientation; placement comes from the Positional/RandomPosition flags plus RandomRangeX/RandomRangeY. |
| Silent Sound Lists | An entry is pushed into playable memory only where the file supplied a Sound resref. Missing entries are ignored rather than erroring. |
Almost every scalar carries over rather than defaulting to a literal
An absent field falls back to whatever the object already holds, so the constructor supplies the value rather than the read site:
| Field | Constructed | Field | Constructed |
|---|---|---|---|
Active | 1 | Interval | 0 |
Positional | 1 | IntervalVrtn | 0 |
Looping | 0 | MinDistance | 10.0 |
Volume | 127 | MaxDistance | 20.0 |
VolumeVrtn | 0 | Continuous | 0 |
Times | 3 | Random | 0 |
PitchVariation | 0.0 | FixedVariance | 1.0 |
Hours | 0 | RandomPosition | 0 |
GeneratedType | 0 | RandomRangeX | 0.0 |
RandomRangeY | 0.0 |
Tag works the same way, starting empty, and its result is re-applied through SetTag either way.
Position is the exception. XPosition, YPosition and ZPosition fall back to a fixed literal 0.0 rather than the carried-over value, applied through SetPosition unconditionally. For a sound placed through a .git the instance’s own values win, so that 0.0 surfaces only for a sound opened outside the placement path.
The return value is whichever field was read last
Load returns the found-flag of its last read: the final Sound resref in the Sounds list, or ZPosition where the list was absent or empty. The area-level save loader deletes the sound object outright when that flag comes back false.
A vanilla sound always carries a ZPosition, so this never bites real saves. A hand-authored .git sound entry with neither a populated Sounds list nor a ZPosition is silently dropped on load.
Fields the engine never reads
What a writer should do with each is a separate question, and it has four possible answers: see the engine ignores this is not you may leave it out.
| Finding Type | Explanation |
|---|---|
| Legacy Engine Artifacts | TemplateResRef, LocName, Comment, Elevation, Priority and PaletteID are inherited from Aurora, the BioWare engine Odyssey descends from. CSWSSoundObject::Load reads none of them. |
Implemented Linter Rules (Rakata-Lint)
Phase 1 (intra-resource, no context)
Implemented under rakata_lint::rules::uts.
- UTS-001 (Volume Ceiling): Warns when
Volume > 127; values outside the engine’s byte threshold cause distortion or clipping. - UTS-002 (Audio Integrity): Warns when the
Soundslist contains blank entries; the engine skips them silently. - UTS-003 (Emitter Verification): Errors when the
Soundslist is empty; the object loads as a dead audio node. - UTS-004 (GeneratedType Truncation): Errors when
GeneratedType > 255; the engine keeps the low byte alone, so the stored type is not the one written. - UTS-005 (Legacy Engine Artifacts): Informs when
TemplateResRef,Elevation,Priority, orPaletteIDare populated;CSWSSoundObject::Loadreads none of them.
Phase 2 (resource existence, requires LintContext)
Implemented under rakata_lint::rules::uts_range.
- UTS-006 (Sound Resref Existence): Warns when any non-blank
Sounds[i].Sounddoes not resolve to a.wavresource in the configured sources. Blank entries are skipped (UTS-002 already covers them).
Every label the schema declares
Generated from the schema, so no label can be quietly left out. How to read these tables.
Fields nobody has examined
Whether the engine reads these has not been established, which is not the same as establishing that it does not. Where When absent carries an answer, that half is settled.
| Field | Type | When absent |
|---|---|---|
TemplateResRef | CResRef | NOT EXAMINED; we substitute "" |
Tag | CExoString | NOT EXAMINED; we substitute "" |
LocName | CExoLocString | NOT EXAMINED; we substitute empty |
Comment | CExoString | NOT EXAMINED; we substitute "" |
Active | BYTE | keeps 1 |
Continuous | BYTE | keeps 0 |
Looping | BYTE | keeps 0 |
Positional | BYTE | keeps 1 |
RandomPosition | BYTE | keeps 0 |
Random | BYTE | keeps 0 |
Elevation | FLOAT | NOT EXAMINED; we substitute 0.0 |
MaxDistance | FLOAT | keeps 20.0 |
MinDistance | FLOAT | keeps 10.0 |
RandomRangeX | FLOAT | keeps 0.0 |
RandomRangeY | FLOAT | keeps 0.0 |
Interval | DWORD | keeps 0 |
IntervalVrtn | DWORD | keeps 0 |
PitchVariation | FLOAT | keeps 0.0 |
Priority | BYTE | NOT EXAMINED; we substitute 0 |
Volume | BYTE | keeps 127 |
VolumeVrtn | BYTE | keeps 0 |
Hours | DWORD | keeps 0 |
Times | BYTE | keeps 3 |
PaletteID | BYTE | NOT EXAMINED; we substitute 0 |
FixedVariance | FLOAT | keeps 1.0 |
GeneratedType | DWORD | keeps 0 |
Sounds | List | NOT EXAMINED; we substitute container |
Sounds[].Sound | CResRef | NOT EXAMINED; we substitute "" |
XPosition | FLOAT | stamps 0.0 |
YPosition | FLOAT | stamps 0.0 |
ZPosition | FLOAT | stamps 0.0 |