UTW Format (Waypoint Blueprint)
A .utw file is a waypoint: a named position in an area with nothing visible attached to it. Scripts use them as anchors for patrol routes, spawn locations and camera targets, and a waypoint can also draw a pin on the player’s map.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .utw |
| Magic Signature | UTW / V3.2 |
| Type | Waypoint Blueprint |
| Rust Reference | View rakata_generics::Utw in Rustdocs |
Field Schema
The format’s field families, as an orientation before the full list.
| Category | Covers | Representative fields |
|---|---|---|
| Core Identity | The waypoint’s name and the tag that scripts target | Tag, LocalizedName |
| Spatial Geometry | The map coordinates and facing that creatures or cameras reference | XPosition, XOrientation |
| Map Navigation Notes | Whether the waypoint draws a pin on the player’s mini-map, and the pin’s text | HasMapNote, MapNote |
Engine Audits & Decompilation
Read from CSWSWaypoint::LoadWaypoint at 0x005c7f30 in swkotor.exe. Provenance: derived, not attested. The rows below have not been separately re-derived, so they sit on the reverse-engineering queue.
The load path
| Function | Size | Behaviour |
|---|---|---|
LoadWaypoint | 682 B | The main constructor. It loads the waypoint’s identity, map geometry, and checks for mini-map pins. |
LoadFromTemplate (0x005c83b0) | 134 B | A fallback used when dynamically spawning a waypoint from a script. It is a thin wrapper: open the .utw file’s own GFF, fetch its top-level struct, and hand off to LoadWaypoint. There is no separate field-reading logic for the script-spawn path. |
Rules the engine enforces
Tag and LocalizedName are unconditional stamps: both read with an empty literal default, and the presence flag is captured and never inspected, so an absent field overwrites rather than leaving a prior value. ObjectId never comes from the .utw: the .git list element assigns it, defaulting to 0x7f000000.
The map note is gated twice
If HasMapNote is 0 or missing, MapNoteEnabled and MapNote are never read at all. There is no fallback fetch; they are simply not asked for.
If HasMapNote is 1, both are read with their own defaults. But none of the three values lands on the waypoint unless MapNote itself was genuinely present. An absent MapNote on an otherwise HasMapNote=1 file discards the whole trio silently, leaving the constructed defaults (HasMapNote=0, MapNoteEnabled=0, MapNote empty) as though nothing had been touched.
MapNoteEnabled’s own default, where HasMapNote=1 and MapNote is present, is an unconditional literal 0. Unlike MapNote, its presence is never separately checked; only its value is used.
That discard combination occurs nowhere in a full install’s waypoint placements, so it is hand-authored territory. The path exists and runs as described.
Orientation normalization has its own sentinel
The engine computes the true magnitude of the orientation vector, not the squared value, and calls Vector::Normalize() whenever it is not exactly 1.0. That function guards itself: below a magnitude of 1e-9 it does not divide, and snaps to a facing of (1.0, 0.0, 0.0).
So a waypoint with all three orientation fields absent lands on that exact sentinel rather than producing garbage or a divide by zero.
Warning
This is not the
(0, 1, 0)sentinel used for area-effect orientation. The two object types do not share the normalization call, and their epsilon thresholds and fallback vectors both differ. Neither can be assumed from the other.
Placement is read twice, and the blueprint fields are real
LoadWaypoint unconditionally reads XPosition, YPosition, ZPosition, XOrientation, YOrientation and ZOrientation, applying them through SetPosition/SetOrientation before anything else. Each falls back to a literal 0.0, with no presence check consulted afterward. This runs the same whether the struct came from a .git instance or a .utw blueprint.
A waypoint placed in a .git layout then has its X and Y re-read from the .git, overriding the blueprint, with Z recomputed against the terrain collision mesh through ComputeHeight. That is a second read layered on the first, not a replacement.
A script-spawned waypoint has no GIT instance to override it, so a hand-authored .utw carrying placement fields takes them from the blueprint durably. Vanilla blueprints never author them because no vanilla workflow needs a script-spawned waypoint’s position baked into a template.
A placed waypoint never resolves a blueprint
LoadWaypoints accepts the area’s UseTemplates flag and never inspects it. Every waypoint placed in a .git layout is a full inline read of the waypoint struct.
LoadWaypoint never reads TemplateResRef at all, not even as a discarded read, which is a real difference from doors (see UTD).
LoadFromTemplate matters only for a waypoint a script spawns at runtime, and even there the resref comes from the script’s own CreateObject() argument, its sole caller being ExecuteCommandCreateObject. The TemplateResRef label is never looked up anywhere in waypoint loading.
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.
The engine ignores TemplateResRef, Appearance, PaletteID, Comment, LinkedTo and Description.
Two of those are universal rather than stale. A corpus pass over a full install found TemplateResRef on every waypoint, and LinkedTo on every waypoint, empty in every one. Both are what the toolset writes each time and the engine reads never, so “legacy padding” undersells how consistently they appear.
A writer may drop TemplateResRef and the engine will not notice, but doing so puts a diff on every waypoint in the game. Preserve it on round trip rather than inventing it on creation, since no blueprint exists for it to name. See fields the engine never reads.
LinkedToModule shares a label with UTD’s and nothing else
A handful of .utw files carry a LinkedToModule CResRef, always empty. LoadWaypoint and LoadFromTemplate read only Tag, LocalizedName, position, orientation and the map-note fields.
The LinkedToModule string in the binary is referenced from door and trigger code alone, LoadDoor, LoadDoorExternal, SaveDoor, LoadTrigger, LoadTriggers and SaveTrigger, and never from waypoint code. Waypoints have no area-transition capability at all; the two fields share interned label bytes, not a concept.
A placed waypoint’s Appearance and Description come from nowhere
Git.WaypointList[].Appearance carries a real value in every waypoint entry in a full install, and Description appears in a handful. LoadWaypoint’s field list has no room for either.
Compare Door and Placeable, where a placed instance’s Description is toolset residue but at least comes from the referenced blueprint. Waypoints resolve no TemplateResRef, so there is no blueprint to fall back to either. Neither label is looked up by this loader under any circumstance.
Implemented Linter Rules (Rakata-Lint)
These diagnostics are implemented under rakata_lint::rules::utw.
- UTW-001 (Map Note Double-Gating): Warns when
MapNoteorMapNoteEnabledare populated butHasMapNote=false; this data is silently discarded by the engine. - UTW-002 (Orientation Warnings): Informs when the orientation vector magnitude is not within ~0.001 of 1.0; the engine forcibly normalizes at load.
Pending
- Tag Enforcement: Flags empty
Tagvalues since waypoints are primarily targeted by name from scripts.
Every label the schema declares
Generated from the schema, so no label can be quietly left out. How to read these tables.
What the engine does with each field
| Field | Type | Engine | When absent |
|---|---|---|---|
Appearance | BYTE | never reads it: a placed waypoint has no rendered model to select, and LoadWaypoint’s fully-decompiled field list has no room for it | not one constant; we substitute 0 |
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 | stamps "" |
LocalizedName | CExoLocString | stamps empty |
HasMapNote | BYTE | keeps 0 |
MapNoteEnabled | BYTE | stamps 0 |
MapNote | CExoLocString | keeps empty |
PaletteID | BYTE | NOT EXAMINED; we substitute 0 |
Comment | CExoString | NOT EXAMINED; we substitute "" |
LinkedTo | CExoString | NOT EXAMINED; we substitute "" |
Description | CExoLocString | NOT EXAMINED; we substitute empty |
XPosition | FLOAT | stamps 0.0 |
YPosition | FLOAT | stamps 0.0 |
ZPosition | FLOAT | stamps 0.0 |
XOrientation | FLOAT | not one constant; we substitute 0.0 |
YOrientation | FLOAT | not one constant; we substitute 0.0 |
ZOrientation | FLOAT | not one constant; we substitute 0.0 |