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

UTW Format (Waypoint Blueprint)

Description: The Waypoint (.utw) blueprint defines static reference coordinates within an area map. Unlike functional triggers or physical placeables, waypoints act exclusively as invisible logic markers. They provide coordinate anchors for creature patrol routes, spawn locations, camera focal points, or visible map pins in the player’s UI.

At a Glance

PropertyValue
Extension(s).utw
Magic SignatureUTW / V3.2
TypeWaypoint Blueprint
Rust ReferenceView rakata_generics::Utw in Rustdocs

Data Model Structure

Rakata maps a Waypoint into the rakata_generics::Utw struct. The struct’s Rustdocs document every field’s binary schema and GFF mapping; the table below is the high-level anatomy.

CategoryCoversRepresentative fields
Core IdentityThe waypoint’s name and the tag that scripts targetTag, LocalizedName
Spatial GeometryThe map coordinates and facing that creatures or cameras referenceXPosition, XOrientation
Map Navigation NotesWhether the waypoint draws a pin on the player’s mini-map, and the pin’s textHasMapNote, MapNote

rakata-lint validates these fields against the engine constraints documented below.

Engine Audits & Decompilation

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

(Decompilation logic for this section was audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CSWSWaypoint::LoadWaypoint at 0x005c7f30.)

Structural Load Phasing

FunctionSizeBehavior
LoadWaypoint682 BThe main constructor. It loads the waypoint’s identity, map geometry, and checks for mini-map pins.
LoadFromTemplate (0x005c83b0)134 BA 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.

Core Structural Findings

Engine RuleRuntime Behavior
Map Note Two-Gate PatternThe skip covers more than the note text. If HasMapNote is 0 or missing, the engine never even attempts the read for MapNoteEnabled or MapNote – there’s no fallback fetch for either, they’re simply not asked for. If HasMapNote resolves to 1, both MapNoteEnabled and MapNote are read (each with its own default if individually absent), but none of the three values – not HasMapNote, not MapNoteEnabled, not MapNote – actually lands on the waypoint unless MapNote itself was genuinely present in the file. Absent MapNote on an otherwise-HasMapNote=1 file discards the whole trio silently, leaving the waypoint at its constructed defaults (HasMapNote=0, MapNoteEnabled=0, MapNote empty) exactly as if none of the three fields had been touched.
Orientation NormalizationThe engine computes the true magnitude of the orientation vector (not just the squared value) and calls Vector::Normalize() whenever it isn’t exactly 1.0. That function has its own epsilon guard: below a magnitude of 1e-9 it doesn’t divide at all – it snaps straight to a sentinel facing of (1.0, 0.0, 0.0). So a waypoint with all three orientation fields absent (a zero vector) doesn’t produce garbage or a divide-by-zero; it lands on that exact sentinel. This is a different code path and a different fallback than the (0, 1, 0) sentinel documented elsewhere in this codebase for area-effect objects’ orientation – the two object types don’t share the normalization call, and their epsilon thresholds and fallback vectors both differ, so don’t assume one from the other.
Position OverrideWhen a waypoint is loaded from a .git area layout via LoadWaypoints, the engine re-reads the X and Y coordinates directly from the .git file, completely overriding the .utw. It also forcefully calculates the Z height based on the terrain collision mesh via ComputeHeight. This is a second read layered on top of the one below, not a replacement for it: LoadWaypoint itself always reads position and orientation first, from whichever struct it’s handed, blueprint or GIT instance alike.
Dynamic IdentificationWaypoints never pull an ObjectId from their own .utw file. It is always forcibly assigned by the .git list element (defaulting to 0x7f000000).
No Template Path for Placed WaypointsThe area’s UseTemplates flag – which switches triggers, stores, and sounds between a save-snapshot read and a template-blueprint read – is accepted by LoadWaypoints but never inspected. Every waypoint placed in a .git layout is always a full inline read of the waypoint struct. Confirmed by decompilation: LoadWaypoint never reads a field named TemplateResRef at all, not even a discarded read – unlike doors (see UTD), a placed waypoint genuinely never resolves a blueprint. LoadFromTemplate only matters for a waypoint a script spawns dynamically at runtime, and even there the resref it opens comes from the script’s own CreateObject() argument (its sole caller is ExecuteCommandCreateObject), not from a TemplateResRef GFF field – that field name is never looked up anywhere in the waypoint-loading code, under any circumstance.
Blueprint Placement Fields Are Real, Not GIT-ExclusiveLoadWaypoint unconditionally reads XPosition, YPosition, ZPosition, XOrientation, YOrientation, and a sixth field rakata does not currently model, ZOrientation – a full 3-component orientation vector, not the 2-component pair the typed struct exposes – and applies all of them via SetPosition/SetOrientation before it reads anything else. Each of the six falls back to a literal 0.0 if individually absent, with no presence check consulted afterward; see “Orientation Normalization” above for what a fully-absent orientation vector resolves to once normalization runs. This runs identically whether the source struct came from a .git instance or a .utw blueprint opened through LoadFromTemplate. A script-spawned waypoint (LoadFromTemplate) has no GIT instance to override it afterward, so a hand-authored .utw carrying these fields would have its placement taken from the blueprint directly and durably, not transiently. Only a waypoint placed in a .git area layout gets the X/Y/Z override described above; vanilla blueprints simply never author the fields because there’s no vanilla workflow that needs a script-spawned waypoint’s own position baked into the template.
Tag and LocalizedName Are Unconditional StampsBoth are read with an empty-value literal default (empty string for Tag, an empty LocalizedString for LocalizedName) and applied unconditionally – LoadWaypoint’s own presence flag for each read is captured and then never inspected. An absent Tag doesn’t leave a prior value in place; it overwrites with a literal empty string every time, the same for LocalizedName.

Legacy & Ignored Data

Finding TypeExplanation
Superseded Legacy FieldsOlder asset revisions pad the file with fields like TemplateResRef, Appearance, PaletteID, Comment, LinkedTo, and Description. The KOTOR engine completely ignores these.
LinkedToModule Shares a Label With UTD’s, Not Its BehaviourA handful of .utw files carry a LinkedToModule CResRef, always empty. LoadWaypoint and LoadFromTemplate were both fully decompiled and read only Tag, LocalizedName, position, orientation, and the map-note fields – nothing resembling an area-transition field. The LinkedToModule string that does exist in the binary is referenced exclusively from Door and Trigger code (LoadDoor, LoadDoorExternal, SaveDoor, LoadTrigger, LoadTriggers, SaveTrigger), never from waypoint code. Waypoints have no area-transition capability in this engine at all; the two fields merely share interned label bytes, not a concept.
A Placed Waypoint’s Appearance and Description Have No Source At AllGit.WaypointList[].Appearance (a BYTE) is present with a real value in every waypoint entry in a full install, and Description shows up in a handful too – but LoadWaypoint’s full field list, confirmed above, has no room for either. Compare Door and Placeable, where a placed instance’s Description is toolset residue but the value at least comes from somewhere (the referenced blueprint) – waypoints resolve no TemplateResRef at all, so there’s no blueprint to fall back to either. A placed waypoint’s Appearance and Description are read from nowhere, full stop: not the instance, not a template, because neither field name is ever looked up by this loader under any circumstance.

Implemented Linter Rules (Rakata-Lint)

These diagnostics are implemented under rakata_lint::rules::utw.

  1. UTW-001 (Map Note Double-Gating): Warns when MapNote or MapNoteEnabled are populated but HasMapNote=false; this data is silently discarded by the engine.
  2. 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 Tag values since waypoints are primarily targeted by name from scripts.