UTC Format (Creature Blueprint)
A .utc file is a creature. Every NPC, enemy, droid and companion in the game
starts life as one of these blueprints: a template that says who the creature
is, what it looks like, how strong it is, what it carries, and which scripts
fire when it notices you or takes a hit.
UTC is one of the formats built on GFF (Generic File Format), the engine’s labelled key/value tree. If you have not read the GFF page yet, start there. This page assumes you know what a field label and a struct are.
You will touch a .utc when you want to change a creature’s stats, swap its
appearance, give it different gear, or hook a new script to it. You will touch
the saved form of one when you edit a character mid-playthrough.
This page documents UTC’s field defaults, the class-boundary split between
CSWSCreatureandCSWSCreatureStats, and the save-versus-template load paths. Evidence is drawn from Ghidra decompilation ofswkotor.exe(K1 GOG build), cross-checked against a full K1 install’s vanilla.utccorpus and real save files. The tables below are lookup surfaces, meant to be searched rather than read start to end.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .utc |
| Magic Signature | UTC / V3.2 |
| Type | Creature Blueprint |
| Rust Reference | View rakata_generics::Utc in Rustdocs |
Why this page is the long one
Two design decisions in the engine account for nearly everything surprising here, and if you hold them in mind the rest of the page stops looking like a list of exceptions.
One loader serves both a blueprint and a save. ReadStatsFromGff has no
branch distinguishing “fresh creature from a template” from “restore this
character”. So a field documented as save-only is often read on the blueprint
path too, and a bug on one path is a bug on both. The differences that do exist
are usually one level up, in the caller, not in the field handling.
A creature is constructed first and the file is overlaid onto it. Nothing here is built from the file. An absent field almost always means “keep what the constructor put there”, which is why so much of this page is about values you never see written down anywhere.
Creatures also hold more state than any other template, so there is simply more surface.
Blueprints ship inside module archives and the BIF files a chitin.key points
at. The Aurora toolset produces them, and every mod tool since has followed its
lead.
The same field set appears again inside a save game. When the engine stores a
module it writes each live creature into that module’s GIT as a full
snapshot, not as a reference back to the blueprint. Those snapshots carry
runtime state a static blueprint never populates. See the
Save Game Deep Dive for how the pieces fit
together.
Field Schema
The format’s field families, as an orientation before the full list.
| Family | Covers | Representative fields |
|---|---|---|
| Core statistics | Base stats defining physical capability | Strength, Dexterity, HitPoints |
| Identity and graphics | Who the creature is, which model it uses | Tag, Appearance_Type, Conversation |
| Class and skill progression | Level, classes, skills | ClassList, SkillList |
| Combat capabilities | Feats and Force powers | FeatList, SpellList |
| Inventory and equipment | Spawn gear, both equipped and carried | Equip_ItemList, ItemList |
| Event hooks | Scripts that fire on world events | OnNotice, OnDamaged |
Engine Audits & Decompilation
The two functions that matter most are
CSWSCreatureStats::ReadStatsFromGff at 0x005afce0 and its save-side
counterpart CSWSCreatureStats::SaveStats at 0x005b1b90.
(Provenance: derived, not attested. These rows sit on the reverse-engineering queue. Where a specific claim is weaker than the rest, we say so inline.)
How the engine loads a creature
These functions do the work. ReadStatsFromGff is the big one.
| Function | Size | What it does |
|---|---|---|
ReadStatsFromGff | 7835 B | Parses the basic creature scalars: strength, dexterity, physical appearance and the rest. |
LoadCreature | Sets up how the creature sits in the world: stealth state, collision size, idle animations. | |
CSWSCreature::ReadScriptsFromGff | Attaches the event scripts that fire on notice, damage, death and heartbeat. A genuine member of CSWSCreature, confirmed by its decompiled __thiscall signature, not a free function. | |
ReadItemsFromGff | Pulls loot into memory, sorting items into equipped slots or the backpack. | |
ReadSpellsFromGff | Extracts the Force powers and combat feats the creature may use. |
You may have read elsewhere that ReadItemsFromGff drops everything if a
creature spawns dead. That framing does not survive the decompile. No branch
anywhere in this call graph inspects hit points or a dead/alive state. See
what actually drops an item entry.
Which class owns which field
A creature’s fields split across several engine classes. That matters because the split decides which function reads a given label, and each function brings its own defaulting rules. Two fields sitting next to each other in the file can behave differently on absence purely because different classes own them.
ReadStatsFromGff reads the bulk of a creature’s identity inline: the
abilities, HP and FP pools, appearance and portrait fields, faction, challenge
rating, AI state, and perception range. It reads ClassList and LvlStatList
inline too. No separate “read class info” delegate exists on the load side,
even though the save side modularizes the equivalent work into SaveClassInfo.
Two structured exceptions delegate out on load: CombatRoundData goes to
CSWSCombatRound::LoadCombatRound, and the nested CombatInfo struct goes to
CCombatInformation::LoadData.
That is a large, coherent chunk of the shared field set. It is not all of it. A
comparable amount lives one level up, read and written inline by
CSWSCreature::SaveCreature and LoadCreature with no CSWSCreatureStats
involvement:
DetectMode, StealthMode, CreatureSize, IsDestroyable, IsRaiseable,
DeadSelectable, AmbientAnimState, Animation, CreatnScrptFird,
PM_IsDisguised, PM_Appearance, Listening, the full set of Script* event
hooks, position and orientation, AreaId, and JoiningXP.
FollowInfo goes further still, delegated from SaveCreature to a fourth
class, CSWSCreaturePartyFollowInfo::Save.
PerceptionList is the one field confirmed to split across classes in
different directions. ReadStatsFromGff reads it, but SaveCreature writes
it, directly and without delegating back to CSWSCreatureStats.
So CSWSCreatureStats is a real class boundary, owning everything under
Save-game snapshot fields plus the class, skill,
feat and power progression. But no single class owns the shared field set: it
spans CSWSCreatureStats, CSWSCreature, CCombatInformation,
CSWSCombatRound and CSWSCreaturePartyFollowInfo, plus two unnamespaced free
functions. That is why a Rust type modelling the whole block needs a
rakata-invented name rather than a borrowed one.
Constructed defaults on the CSWSCreature side
Most CSWSCreature-owned fields construct to a plain literal and stay there.
StealthMode, CreatureSize, AmbientAnimState, CreatnScrptFird,
PM_IsDisguised, PM_Appearance and JoiningXP all zero-initialize in
CSWSCreature’s own constructor.
Two are overridden inside that same constructor and do not end where they
start. Animation takes 10000 from the base CSWSObject constructor and is
immediately re-set through SetAnimation, landing at 10001. DetectMode
zero-initializes and is then bumped to 1 by an internal SetDetectMode call.
The base class CSWSObject, not CSWSCreature, owns several more:
| Field | Constructed value |
|---|---|
IsDestroyable | 1 |
IsRaiseable, DeadSelectable, Listening | 0 |
| position | the origin, (0.0, 0.0, 0.0) |
| orientation | (1.0, 0.0, 0.0), three components rather than two |
AreaId | the object-reference sentinel 0x7F000000, not a plain 0 |
Important
LoadCreaturedoes not use those constructed values as its absent-field fallback. Its defaults are hardcoded literals baked into each read call site rather than live reads of the object’s current field. Several disagree with what the constructor sets:
Field Read fallback Constructed CreatureSize30IsRaiseable10DeadSelectable10Animation1000010001AreaId00x7F000000For a save missing one of those, the creature ends up at the read literal, not at what a fresh
CSWSCreaturewould hold. The read default is the correct absent-field answer there.
DetectMode’s read default is a mismatched0as well. It reconciles only because the constructor’sSetDetectMode(1)runs afterLoadCreaturereturns, not because the read carries the true value.For the rest of the group (
IsDestroyable,StealthMode,AmbientAnimState,CreatnScrptFird,PM_IsDisguised,PM_Appearance,Listening) the literal and the constructed value coincide, so the split is invisible. It is still a distinct mechanism, not a coincidence that generalises.
Position, orientation and JoiningXP are not read by LoadCreature at all.
They come through CSWSArea::LoadCreatures and LoadFromTemplate instead.
Both use their own literal 0.0 position default, matching the constructed
origin, and both apply orientation only when the read value is nonzero. That is
a presence-adjacent gate, not a plain carry-over. A template-spawned creature
with no orientation in the file keeps the constructor’s (1.0, 0.0, 0.0)
facing, but by that separate mechanism rather than by LoadCreature’s own
defaulting.
Rules that crash the game
Most of a creature’s numbers are not values. They are row indices into 2DA
tables: Race indexes racialtypes.2da, Appearance indexes
appearance.2da, a resolved movement rate indexes creaturespeed.2da. The
file supplies the index and the table supplies the meaning.
That is why the failures here are crashes rather than warnings. A row index that does not name a row has no fallback meaning to reach for, so the engine notices and gives up instead of continuing with a creature it cannot describe.
Warning
Fatal crash codes (
0x5fX) When the engine parses a file and hits an invalid stat, it aborts loading entirely. Instead of recovering, it triggers a fatal crash to desktop and returns a hexadecimal error code such as0x5f7or0x5f4. The rules below track the scenarios that produce one.
| Rule | Runtime behaviour |
|---|---|
| Class identity | A duplicate class id in ClassList crashes the game (0x5f7). A resolved-but-out-of-range class id crashes with the same code, not a separate failure mode. |
| Race bounds | The engine compares Race against the compiled row count of racialtypes.2da. Exceeding it fatally crashes the map loader (0x5f4). |
| Saves calculation | Pre-computed saving throws (SaveWill, SaveFortitude) in the file are ignored dead data. The engine reads willbonus and fortbonus instead. |
| Perception faults | A non-PC PerceptionRange triggers a read against appearance.2da for PERCEPTIONDIST. Failing to resolve that distance fails the whole creature load (0x5f5). |
| Hard clamping | Gender clamps structurally at a maximum of 4. GoodEvil clamps so it cannot exceed 100. |
| Appearance shifting | An Appearance_Head of 0 is overridden to 1. |
PerceptionRange takes the fault-prone path by default. The field is never
read at all for a PC. For a non-PC, an absent PerceptionRange defaults to a
hardcoded literal 11, not a carried-over value. That 11 is itself the
sentinel routing into the appearance.2da lookup above. So
an absent field lands on the failure-prone branch by construction, not by
coincidence. Any other resolved value is used directly as a ranges.2da row
index instead.
Appearance_Head fires on the resolved value. The check never asks whether
the field was present, so it behaves the same for an explicit 0 and for an
absent field’s carried-over default. Almost no vanilla .utc carries the
field, so nearly every creature takes the carried-over 0 and is bumped to
1. No creature can end up stored at 0.
WalkRate is read only where MovementRate was absent. Where
MovementRate is present, the WalkRate read is skipped outright rather than
performed as a no-op.
Both movement fields absent lands on creaturespeed.2da row zero
Every shipped creature omits both MovementRate and WalkRate. The
constructor’s zero-initialized value survives both reads and reaches the
movement-rate setter as a literal 0.
That 0 is not a speed. It is a row index into creaturespeed.2da, whose
WALKRATE and RUNRATE columns hold the actual floats.
The setter carries one sentinel. A resolved value of exactly 7, which the
both-absent case cannot reach, redirects to an appearance.2da and
creaturespeed.2da name lookup instead of indexing directly.
Absent-field defaults
The engine does not build a creature from the file. It builds a creature first, then overlays the file onto it.
That is the single idea this whole section rests on. ReadStatsFromGff runs
against an object whose constructor has already set every member to something.
Most reads pass the member’s own current value in as the fallback and then
stamp the result back unconditionally, so an absent field resolves to whatever
construction left there. We call that carry-over.
Two consequences follow:
- Absent does not mean zero. It means “whatever the constructor chose”, and
the constructor does not always choose zero.
HitPointsstarts at1, the script hooks start at"default",AreaIdstarts at a sentinel. - The same file loads differently onto a used object. On a fresh blueprint load carry-over is a no-op, because the construction value is all that was there. On a save reload onto an object that already holds state, an absent field silently preserves that state.
These apply on any load, blueprint or save alike, because ReadStatsFromGff
does not branch on its caller. The fields that break the pattern each get a
heading below.
The core abilities default to 0, not a tabletop 10
Str, Dex, Con, Int, Wis and Cha all follow one identical
mechanism: each is read with the object’s own current value as the fallback and
stamped back unconditionally. The constructor initializes every one of them to
a literal 0 before any read happens.
So a .utc missing an ability score does not fall back to a sensible tabletop
default. It resolves to 0, not 10.
Identity, appearance and state
These all use plain carry-over:
Tag, Conversation, Deity, Description, Age, StartingPackage,
Subrace, SubraceIndex, Color_Skin, Color_Hair, Color_Tattoo1,
Color_Tattoo2, Phenotype, Appearance_Type, DuplicatingHead,
UseBackupHead, FactionID, ChallengeRating, NaturalAC, Min1HP,
PartyInteract, Disarmable.
Some need a qualifier:
| Field | Qualifier |
|---|---|
Gender | This is its own absent default, separate from the clamp to 4 above. |
GoodEvil | Likewise separate from the clamp to 100 above. |
AIState | Read through the INT reader despite being a WORD field, and truncated on store. |
WalkRate | Its own default is the object’s current movement_rate, separate from the MovementRate falls back to WalkRate rule above. |
Portrait | Reached only conditionally. See PortraitId below. |
Naming corrections
The colour fields are underscored: Color_Skin, not ColorSkin. The type
field is Phenotype, not PhenoType. SubraceName does not exist as a GFF
label at all. The two real, distinct labels are Subrace (the free-text name)
and SubraceIndex (the numeric id).
TemplateResRef is not read by ReadStatsFromGff at all
TemplateResRef never appears anywhere in ReadStatsFromGff. It is read one
level up, by CSWSArea::LoadCreatures, and only on the branch resolving a GIT
Creature List entry’s blueprint reference. The direct save-instance path has
no use for it.
The read itself defaults to an empty resref, but the loader explicitly checks
the presence flag. If TemplateResRef is absent, it destroys the just-allocated
creature object and drops the entry entirely. LoadFromTemplate, and therefore
ReadStatsFromGff, never runs for that entry.
This is a genuine presence-chain abort. The identical pattern, same field and
same behaviour, governs blueprint resolution for triggers, placeables, items,
doors, encounters and sounds. A templated GIT entry missing its own
TemplateResRef is dropped outright, not defaulted.
FirstName and LastName
Neither carries over. Both are an unconditional blank stamp, unlike the
structurally identical Description a few lines away.
Description’s fallback is the object’s own current value
(&this->description). FirstName and LastName are each read against a
freshly default-constructed, empty localized string instead, with no reference
to the object’s prior name.
An absent FirstName or LastName overwrites whatever name the object held
with an empty one. It does not leave a prior name in place, the way
Description and nearly everything else on this page does.
SoundSetFile
It is not a resref. It reads with ReadFieldWORD as a soundset.2da row
index, and it lives on the owning CSWSCreature rather than
CSWSCreatureStats, matching the class boundary above.
It constructs to -1 (0xFFFF as a WORD), the same “nothing assigned”
sentinel as the WORD-indexed PortraitId and FactionID. That is not the
0x7F000000 object-reference sentinel used elsewhere on this page.
PortraitId
Its default is a literal sentinel 0xFFFF, not a carry-over. Unlike most
fields here, PortraitId’s fallback is a fresh literal rather than the
object’s current value.
0xFFFF fails the same < 0xFFFE check documented for the explicit 0xFFFE
sentinel. So an absent PortraitId behaves identically to writing 0xFFFE
explicitly. Both route to the Portrait resref path, which itself carries over
the object’s current value when absent.
SkillPoints is read twice, at two struct scopes
A typed view needs both reads: one flat on the top-level struct, and one per
entry inside LvlStatList under the same label.
Both construct to 0, by different mechanisms. The flat read is the ordinary
idiom, defaulting to the top-level member’s own constructed value. The
per-entry read borrows that same top-level member rather than the freshly-built
level-up record’s own field, because the loop runs before the flat read while
the top-level member still holds its constructed value.
The level-up record’s field independently constructs to 0 too, so nothing
diverges in practice. The mechanism is still unique on this page: nowhere else
does a sibling’s value stand in for a field’s own.
Plot and Invulnerable
Plot has an undocumented legacy-label fallback, the same shape as
MovementRate to WalkRate. The loader first tries a field literally named
Invulnerable. Only if that is absent does it fall back to trying Plot by
name.
Either way the result lands in the same plot member, written unconditionally,
with a final fallback of the object’s own prior plot value if neither label
is present.
Invulnerable is a real, distinct GFF label, the same one already documented
as read by LoadDoor and LoadPlaceable for their own objects. It takes
priority over Plot for creatures specifically.
NotReorienting
It round-trips through a polarity inversion, which is not a bug. The
GFF-visible field is the logical negation of the internal reorienting member
on both read and write. The double negation cancels out algebraically.
Worth knowing only if you are comparing the label’s sense to the internal state directly.
The script hooks default to "default"
They follow the mechanism already confirmed for doors and triggers.
CSWSCreature’s constructor pre-arms every script slot to the literal string
"default" before any GFF read happens, and each read’s own fallback is the
slot’s current value. So an absent hook resolves to "default", not empty.
The hooks are ScriptHeartbeat, ScriptOnNotice, ScriptSpellAt,
ScriptAttacked, ScriptDamaged, ScriptDisturbed, ScriptEndRound,
ScriptDialogue, ScriptSpawn, ScriptRested, ScriptDeath,
ScriptUserDefine, ScriptOnBlocked (not ScriptBlocked), and
ScriptEndDialogue, which truncates on disk to ScriptEndDialogu under the
16-byte GFF label limit.
Confirmed for both entry points: LoadFromTemplate for a fresh .utc
blueprint load, and LoadCreature for a save reload, with no re-arming between
construction and either path.
Skills, classes and powers
SkillList
SkillList is not eight labelled fields. It is a GFF list of eight positional
entries, one Rank byte each, in skills.2da row order. rakata models it as
UtcSkills.
Absent and present-but-empty are different, and the difference only shows on an object that already holds ranks:
SkillList | Effect |
|---|---|
| Absent | The block is skipped and existing ranks stay untouched. |
| Present, including empty | All eight positions are force-zeroed first. |
Where the list is present, a position with no entry of its own takes a default
computed live from the engine’s skill-check function: ability modifier plus any
feat bonus already applied, not a flat 0. The raw component is 0 at that
point so it reduces to zero in practice, but the mechanism is sibling-derived
rather than literal.
ClassList
Class carries over the slot’s existing class id, applied only if the field
was present and does not resolve to the -1/NONE sentinel. Otherwise the
slot’s existing id stands untouched.
ClassLevel also carries over, and its own read is gated: it is attempted only
if the slot already resolved to a valid, non-NONE class earlier in the same
pass.
Freshly-constructed baseline values: slot 0 defaults to Soldier at level 1,
slot 1 to NONE at level 0.
A creature has two class slots, and that is a property of the object rather
than of the file. A ClassList carrying more entries than that has nowhere
to put the extras. Note that this is a different situation from the two
0x5f7 crashes above: a duplicate id and an out-of-range id are both fatal,
while a third valid, distinct entry is not. See
Open questions for how firmly we have established that.
LvlStatList
The per-level ledger: one entry per level the character has taken, extended one
entry at a time by CSWSCreatureStats::LevelUp during ordinary play. For the
player character it is also what the hit point and force pools are summed from,
rather than from ClassLevel. See Write-only fields.
Every field in an entry reads with the entry’s own current value as its fallback, so an absent field leaves whatever the entry already held, and a short or missing list does not abort the load.
| Label | What an absent field leaves behind |
|---|---|
LvlStatAbility | carries over. Constructs to 6 |
LvlStatHitDie | carries over. Constructs to 0 |
LvlStatForce | carries over. Constructs to 0 |
LvlStatClass | carries over. Constructs to 0 |
SkillPoints | the creature’s top-level SkillPoints, not the entry’s own |
SkillList[].Rank | carries over, per skill |
FeatList | untouched. Presence-gated per entry |
KnownList0 | untouched. Append-only, never cleared |
KnownRemoveList0 | untouched. Append-only, never cleared |
LvlStatAbility’s constructed 6 is a sentinel rather than a value. The
valid ability range is 0 through 5, so a 6 matches no case and applies no
bump. SaveClassInfo agrees from the other side: it emits the label only when
the stored value is not 6. Absent and an explicit 6 are the same state at
both ends.
The per-entry SkillPoints borrows its default from outside the entry. It
falls back to the creature’s top-level SkillPoints rather than to the entry’s
own prior value, because that loop runs ahead of the flat read later in the same
pass. Both resolve to 0 today only because the top-level member constructs to
zero, so a file that sets the top-level field while omitting it on an entry
would diverge.
A ledger entry’s nested lists are inert on load. FeatList, KnownList0
and KnownRemoveList0 inside an entry are separate storage from their
same-named counterparts at the top level, and nothing re-applies them to the
creature. They become live only when a live level-up consumes that entry.
Struct ids, for anyone writing this list. SaveClassInfo uses a flat
literal per list rather than a running counter. Every LvlStatList element
carries 0, as does every element of a ledger entry’s own FeatList,
KnownList0 and KnownRemoveList0. At the top level a ClassList element
carries 2 and a FeatList element 1, and a KnownList0 nested inside
ClassList carries 3. So KnownList0 means two different ids depending on
which depth wrote it.
KnownList0 and SpellsPerDayList
Per-class powers are read by a separate function, not inside the ClassList
loop. CSWSCreatureStats::ReadSpellsFromGff runs after ReadStatsFromGff
returns, from both LoadFromTemplate and LoadCreature, and re-walks
ClassList independently.
It confirms directly, not just as an observed file convention, that the
per-class known-power list label is always built literally as "KnownList" + 0
regardless of which class index is being processed.
An absent or empty KnownList0 leaves that class with zero known powers, with
no abort. Within a present list, each power’s Spell field defaults to the
sentinel 0xFFFF. When a power resolves to that sentinel, explicitly or by
absence, the whole power entry is skipped and never appended. That is a
presence-chain abort at the individual-power level, not a defaulted 0 power.
A related but distinct read lives inside the same ClassList loop, not in
ReadSpellsFromGff: a Jedi-only SpellsPerDayList and NumSpellsLeft block
for uses-per-day bookkeeping. An absent list is a soft no-op, and only the
first entry of that list is ever applied. Further entries are read and
discarded.
SpecAbilityList
Spell, SpellFlags and SpellCasterLevel each independently default to a
literal 0, unconditional, with no presence gate on any of the three.
An entry is appended as soon as the list-element fetch succeeds. There is no scenario where a kept entry gets dropped for missing scalar fields.
FeatList
Feat defaults to 0, but AddFeat is called only where the field was
present, so an absent Feat contributes nothing for that list position. That
is a presence-chain abort scoped to the single entry.
(We could not resolve the top-level call site’s own presence-flag register
with certainty from the decompiler output. The reading comes from the identical
idiom in the per-level FeatList inside the PC LvlStatList loop.)
At the list level, FeatList does not share SkillList’s absent-versus-empty
asymmetry. The list is iterated only once the field is found and its element
count is nonzero, both checked together. So an absent FeatList and a
present-but-empty one hit the same skip and leave existing feats untouched.
There is no equivalent of SkillList’s force-zero-then-refill anywhere in this
function. That step exists for skills because the eight ranks are a fixed-size
array needing a defined baseline. Feats have no fixed slots to reset. The two
diverge for that structural reason, so the skill behaviour does not transfer.
Item lists
Equip_ItemList is one flat GFF list, not per-slot numbered fields.
Equip_ItemList0, Equip_ItemList1 and so on do not exist. The equip slot is
the list element’s own struct id, read structurally off the GFF element header
through CResGFF::GetElementType rather than from any field. It has no
“absent” state to document.
ItemList does the opposite in the same loader. It is walked purely by
position and its element struct ids are never read. So the sequential values
vanilla writes there carry nothing, and a writer is free with them in a way it
is not with Equip_ItemList’s.
EquippedRes and InventoryRes
Both share the same fate on absence. Each is presence-gated, and if the field
is missing, or present but does not resolve to a real .uti blueprint, the
freshly-allocated item object is destroyed on the spot and the loop moves on.
That is a genuine presence-chain abort: a resref-less entry is dropped
entirely, not kept with an empty resref.
Dropable is unconditional on both lists. A literal 0, meaning not
droppable, is stamped regardless of presence, with no gating.
One thing that looks like an absent-field question but is not: if an equipped
item fails the CanEquipItem slot check after loading successfully, it is not
discarded. It is rerouted into the creature’s backpack. That is a post-load
routing decision, not a defaults question.
ObjectId, and the one thing that drops an entry
An entry can be dropped outright, and only on the save-reload path
(LoadCreature, which is unreachable from a standalone .utc blueprint load).
Each entry can carry an ObjectId pointing at an already-instantiated item.
The loader silently skips the entry where that item’s live possessor is not the
creature being loaded: no equip, no backpack add, nothing.
That is plausibly the origin of the “spawns dead” framing, since items reassigned to a corpse or loot container after death would trigger exactly this mismatch. The real mechanism is narrower: a stale-possessor check specific to save reloads, not a hit-points rule, and it never fires on a fresh blueprint spawn.
Both Equip_ItemList[].ObjectId and ItemList[].ObjectId use the same
object-reference sentinel 0x7F000000, not plain 0. The read runs only on a
save reload. On a fresh blueprint load the loop’s local is pre-initialized to
that same sentinel, so both paths land identically.
Repos_PosX and Repos_Posy are inert here
Both are unread on the creature path, regardless of what a hand-authored file
supplies. The only code reading either label is ReadContainerItemsFromGff,
which serves placeable and store containers, and nothing reachable from a
creature load calls it.
We established this by decompiling every function that touches a creature’s
Equip_ItemList or ItemList entries, and by a binary-wide string search.
That search also settles the casing. The strings in swkotor.exe are
Repos_PosX and lowercase Repos_Posy. There is no Repos_PosY with an
uppercase Y anywhere in the binary, on any object type.
ItemList[].Infinite is a store field
It is not a creature field at all, unlike its identically-named counterpart on
UTM’s own ItemList.
The "Infinite" label string has exactly two cross-references in the whole
binary, both inside CSWSStore::LoadStore and SaveStore. No function
anywhere in a creature’s item-loading call graph reads it.
There is no absent-field default to give here, the same way there is none for a
field that is never looked up. This is a store-exclusive field that happens to
share a label and a parent-list name with UTC’s own ItemList.
Save-game snapshot fields
A creature serialized into a save carries live runtime state a static .utc
blueprint does not usually populate. CSWSCreatureStats::SaveStats emits these
alongside the template fields, and they appear on the creature structs inside a
save’s module GIT.
“Usually” is doing real work in that sentence. ReadStatsFromGff has no branch
distinguishing a blueprint struct from a save-instance struct, so several of
these snapshot fields are genuinely read on the blueprint path too, the same
pattern .ute encounters show. Vanilla .utc files simply never populate
them. Others are write-only on every path, blueprint included.
| Field | Meaning | Read on the blueprint path too? |
|---|---|---|
CurrentHitPoints | Live current HP. | Yes. Unconditional single read site, no UseTemplates-style gate. The absent-field default derives from HitPoints, not from any prior “current HP” state. |
HitPoints | The HP pool CurrentHitPoints derives from. | Yes. Unconditional, carrying over the object’s own hit points. |
MaxHitPoints | Computed HP ceiling. | No. The field-name string has exactly two cross-references in the whole binary, both writers (SaveStats, SaveCharGenCreature). Zero readers anywhere. |
PregameCurrent | Nominally a current-HP mirror. | No. Same exhaustive check: exactly two references, both writers, zero readers, on any path. |
ForcePoints | Live Force-point pool. | Yes. Unconditional, carries over the object’s own constructed value (0) when absent. |
CurrentForce | Live current Force. | Yes. Unconditional, but sibling-derived from ForcePoints when absent, not carried over independently. |
MaxForcePoints | Computed Force-point ceiling. | No. Same exhaustive write-only check as MaxHitPoints. |
The HitPoints carry-over bottoms out at 1. The object constructor sets
hit points to 1 before any GFF field is read. So a blueprint omitting
HitPoints produces a creature on one hit point, and one omitting both fields
produces a creature on one current hit point too.
The remaining snapshot fields:
| Field | Meaning |
|---|---|
RefSaveThrow, WillSaveThrow, FortSaveThrow | Computed saving-throw totals: base plus ability modifier plus active effects. Distinct from the template’s ignored SaveWill and SaveFortitude dead fields. Confirmed write-only by the same exhaustive check as MaxHitPoints. SaveStats computes each fresh at save time, so there is no backing field for an absent-field default to apply to. |
ArmorClass | Computed AC snapshot, via CSWSCreature::GetArmorClass() at save time. Confirmed write-only by the same exhaustive check. |
Experience | Runtime progression, omitted by every vanilla .utc, so every creature carries over the constructor’s 0. That 0 passes through SetExperience, which refuses to lower a creature’s XP by comparing against the current value and storing only where the incoming one is larger. Both sides are 0 here, so the call is a no-op. A plain uncapped counter with no sentinel range. |
Gold | Not a CSWSCreatureStats field. The backing storage is CSWSCreature::gold, proxied through GetGold and SetGold from ReadStatsFromGff and SaveStats. Unlike the fields above, this one genuinely follows the self-referencing carry-over idiom, constructing to 0. |
AIState | Constructs to 0. Stored as a WORD member but read through the INT reader, the truncation quirk already documented. Unconditional carry-over, confirmed genuinely read on the blueprint path too. |
NotReorienting | Not its own field either. The backing storage is CSWSObject::reorienting, and NotReorienting is written and read as its logical inverse. reorienting constructs to 1 (true), so NotReorienting constructs to 0 (false). Confirmed genuinely read on the blueprint path too, unconditional carry-over. |
MClassLevUpIn | Multiclass level-up bookkeeping. Confirmed write-only: computed fresh at save time as class_count - 1 from the live class count, with no backing field to default when the file omits it. A freshly built object would only ever export MClassLevUpIn = 0, because class_count itself constructs to 1. |
Combat state, meaning the active combat round and equipped-weapon data, is
written separately through CCombatInformation::SaveData (0x00550f30, read
back by CCombatInformation::LoadData at 0x00552350).
The class, skill, feat and power progression is written by SaveClassInfo
(0x005aec90) and reflects the creature’s current levelled state, which for
a played character diverges from the blueprint. SaveClassInfo is a genuine
member of CSWSCreatureStats itself, confirmed against the binary’s own class
layout. CCombatInformation is a separate class with no members in common with
CSWSCreatureStats, reached through a nested object the creature owns rather
than through inheritance.
Tail and Wings are a round-trip loss, not inert legacy data
ReadStatsFromGff never looks either label up. There is no ReadFieldBYTE
call for them anywhere in the function. It assigns 0 to both members flat and
unconditionally, overwriting whatever the object held and whatever the file
contains.
That is identical on the blueprint path and the save-instance path, since both
call the same ReadStatsFromGff and nothing in it distinguishes the callers.
SaveStats still writes both out on every save. So whatever a creature’s tail
or wings hold in a save file is discarded the moment it loads back in.
For what a writer should do with them, see
fields the engine never reads.
Dropping them from a blueprint costs nothing. A save writer matching the
engine’s own output writes them as 0.
Write-only fields
Several of the fields above look like round-trip state but are strictly
one-way. SaveStats writes them on every save, and ReadStatsFromGff never
reads one back. (Tail and Wings are in the same club.)
They are one-way for two very different reasons, and the difference decides whether editing one is pointless or actively misleading.
MaxHitPoints, ArmorClass and the three saving-throw totals are
recomputed, not restored. Each is written from a live getter, the same
getters combat rolls and UI displays call at runtime. On load the engine simply
rebuilds the number from inputs that already round-tripped:
| Snapshot total | Rebuilt on load from |
|---|---|
MaxHitPoints | Class levels and the Constitution modifier. For non-PC creatures, the template’s own HitPoints, confirmed to be the literal raw value this same field feeds into CurrentHitPoints’s sibling-derived default. |
ArmorClass | Per-class armour-bonus tables, natural AC, the Dexterity modifier, feat bonuses, and the active effect list, reapplied as the last step of LoadCreature. |
RefSaveThrow / WillSaveThrow / FortSaveThrow | The class and feat base, the ability modifier, the effect bonus, and a permanent-bonus byte that does round-trip, under the lowercase labels refbonus / willbonus / fortbonus, not the capitalized totals. |
So editing any of those totals in a save changes nothing: the engine derives the real numbers from the inputs in the right-hand column. They are snapshots for external tooling, and no state is lost by ignoring them.
The player character’s pools are rebuilt from somewhere else entirely, and
that is the part an editor has to know. The class-levels-and-ability-modifier
rule above is the companion and NPC path. For the actively-controlled player,
MaxHitPoints and MaxForcePoints ignore ClassList’s ClassLevel
completely and sum the per-level LvlStatHitDie and LvlStatForce values out
of LvlStatList, the same ledger CSWSCreatureStats::LevelUp extends one
entry at a time during ordinary play. A LvlStatList shorter than the
character’s level does not abort the load. It pads with zeroed entries,
flooring at one hit point per level.
So raising a player’s ClassLevel without extending LvlStatList to match
gives a character whose pools are wrong from the first load, silently and with
nothing reporting it. The same edit on a companion is harmless, because nothing
on that path consults the ledger. This is the one place where the two creature
kinds diverge on what an input actually is.
The permanent-bonus bytes. refbonus, willbonus and fortbonus are the
raw bytes that do round-trip. Each constructs to 0 on CSWSCreatureStats,
and an absent one on a template load carries that 0 over unconditionally,
the same idiom as ForcePoints and Experience above.
A corpus scan turns up capitalized variants of these labels (FortBonus,
RefBonus, WillBonus) in a handful of .utc files, always holding 0.
These are dead by construction, not merely dead in practice.
GFF field-label lookup is case-sensitive, and the only literal
strings ReadStatsFromGff ever constructs to search for are the lowercase
fortbonus, refbonus and willbonus documented above. A capitalized variant
is never found, read, discarded, or compared against anything.
The same applies to SubRace versus the modelled Subrace. The engine only
ever looks up Subrace, so a SubRace-spelled field is unreachable regardless
of what value it holds.
MClassLevUpIn and PregameCurrent are the genuine dead writes. No reader
exists anywhere in the binary, not even in the character-generation export path
(SaveCharGenCreature) that also writes them. The class count
MClassLevUpIn supposedly bookkeeps is derived from the restored ClassList’s
length instead. PregameCurrent, despite the name, behaves as a
continuously-refreshed current-HP mirror that nothing ever reads back.
Gold: party members do not round-trip it
Gold is written for every creature, but on load the engine skips the read for
anyone currently in the party. A party member’s wealth lives in the shared
PT_GOLD pool in PARTYTABLE.res, and the per-member
snapshots are frozen copies the loader deliberately ignores.
Editing a party member’s Gold in a save does nothing. Edit PT_GOLD instead.
Ordinary NPCs, merchants and corpses round-trip Gold normally.
The full routing design, including how the writer freezes those per-member copies, is covered in Gold and the party pool.
DetectMode: a genuine round-trip bug
Unlike the write-only fields above, DetectMode looks like it should
round-trip and does not. This one reads like an honest bug rather than a design
choice.
SaveCreature writes the live detect-mode value faithfully. On load,
LoadCreature reads a DetectMode byte from the save struct just to advance
past it. The value is never assigned to anything, and construction-time logic
resets every restored creature to detect mode 1 regardless of what the save
contained.
This is an engine quirk to be aware of, not something rakata should “correct”
on read. The on-disk value is real and byte-accurate. The engine’s own loader
simply never consumes it.
JoiningXP: only restored on fresh spawns
JoiningXP shares the same shape as the DetectMode bug. SaveCreature
writes it on every save, but only LoadFromTemplate, the fresh-spawn path used
for template-based creatures, reads it back.
LoadCreature, the loader used for ordinary save-game continuation, never
reads JoiningXP at all. So it silently resets to 0 every time a save is
reloaded.
Structural fields written only when live
FollowInfo (party-follow state) and ExpressionList (listen and expression
data) are written by SaveCreature only when the corresponding live pointer or
list is actually populated.
Their absence from a save is less a defaults question than a statement that there was no runtime state to save. On load, an absent struct means that piece of party-follow or listen-data state stays unallocated.
A few more fields depend on runtime conditions at save time rather than always being present:
| Field | Emitted when | Absent on load resolves to |
|---|---|---|
PM_Appearance | PM_IsDisguised == 1 | 0. The loader only attempts the read if PM_IsDisguised decoded true. |
CombatRoundData contents | Combat was mid-round at save time | The struct header is always present, but SaveStats has no writer counterpart for this data at all. The outer SaveCreature writes the struct shell, and whether the roughly two dozen combat-round scalars inside it were populated depends entirely on whether the game happened to be captured mid-round. |
EffectList, VarTable, SWVarTable, ActionList | List and struct headers are always written; contents reflect however many entries exist | Empty containers restore no effects, script variables, or queued actions. |
As a minor aside: the per-level and per-class known-spell lists are always
labelled KnownList0 and KnownRemoveList0 in the GFF, literally suffixed
with the digit zero rather than substituting the level or class index. Reader
and writer agree on this, so it is internally consistent rather than a bug.
Worth knowing if you are ever diffing raw GFF structs by hand.
Fields the engine never reads
A field the engine never reads is not automatically a field you may leave out. The engine is one consumer among several: the toolset, save managers and other mod tools read these files too, and a blueprint that round-trips through one of them can lose a field the game itself would have ignored.
So “dead” here means “the K1 engine does not read it”, and what a writer should do about that is a separate question. See “the engine ignores this” is not “you may leave it out”.
| Finding type | Explanation |
|---|---|
| Legacy engine artifacts | Fields present in older files are Neverwinter Nights superset metrics the K1 engine does not read. Morale, SaveWill, BlindSpot and PaletteID have no label string in swkotor.exe, so those four are dead by the same test as the row below rather than merely untraced. |
| Confirmed dead by string absence | TemplateList, CRAdjust, SaveReflex and MemorizedList0. |
Some labels have no string in the binary at all
TemplateList, CRAdjust, SaveReflex and MemorizedList0 do not exist as
field-name strings anywhere in swkotor.exe.
That test is worth understanding, because it is the strongest evidence available on this page. GFF lookup works by matching a label against a literal string the reader constructs. If the string is not in the binary, no code path can branch on the field’s presence or read its contents, whatever the file holds. It is not “we did not find a reader”; it is “a reader cannot exist”. The same test settled several dead DLG fields.
TemplateList is worth naming separately. It is a List, present but empty in
every .utc in a full install, and the highest-prevalence unmodelled label in
the corpus.
SaveReflex follows the dead pattern already documented for SaveWill and
SaveFortitude. None of the three raw saving-throw fields exists as a string
to trace an override from, so the live refbonus-style computation superseding
them is (provenance: inferred) for SaveReflex specifically, by analogy with
its two siblings.
Comment and the legacy batch
A binary-wide string-existence check, the same decisive test used for
TemplateList, CRAdjust, SaveReflex and MemorizedList0, confirms that
Comment, Morale, MoraleRecovery, MoraleBreakpoint, PaletteID,
BlindSpot, MultiplierSet, NoPermDeath, IgnoreCrePath, Hologram,
WillNotRender and LawfulChaotic do not exist as field-name strings anywhere
in swkotor.exe.
Comment specifically settles as “not read at all”, not merely “read and
ignored”.
TextureVar and BodyVariation are a related but distinct case. Both strings
genuinely exist in the binary, but their only cross-references are the item
(.uti) loader and saver. They are real field names on a different format,
never read by any creature-related function, and functionally just as dead for
UTC purposes.
BodyBag and Interruptable are genuinely read
Two fields need pulling back out of the “confirmed dead” framing. Both are read
live by ReadStatsFromGff via an ordinary carry-over ReadFieldBYTE call and
stored into real members (creature->body_bag, this->interruptable).
Neither is in this page’s UTC-007 lint list, so there is no existing
contradiction to fix. But do not assume UTC’s Interruptable shares the fate
of the identically-named, confirmed-dead Interruptable field documented on
UTD and UTP. It is a different field on a different format,
and this one is live.
Whether the values these two store are ever consumed downstream, in combat or AI logic, was not traced in this pass.
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,
and the structural reader represents them faithfully.
Stacked SpecAbilityList entries on the Bastila variants
The Bastila 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 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 is taken from CResGFF::GetListCount masked down to a
single byte. 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 (0 to 131), 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 UTC lint rules: for example,
“SpecAbilityList[].Spell must resolve to a row in spells.2da”, and
optionally “warn on stacked-duplicate SpecAbilityList entries unless
explicitly whitelisted as a known vanilla pattern”.
Implemented Linter Rules (Rakata-Lint)
rakata-lint encodes the rules above so you can check a file before the engine
does. The split below is about what a rule needs to run: the first set reads
only the file in front of it, the second needs the install’s 2DA tables and
resource sources to resolve references against.
Where a rule’s wording and this page’s engine notes seem to disagree, the
engine notes win: several rules were written before the decompilation work that
now backs this page. The two class rules are the case worth knowing, because
they look like one rule and are not. UTC-002 counts entries. UTC-003 compares
ids. A ClassList can trip either, both, or neither.
Intra-resource rules needing no context, under rakata_lint::rules::utc:
| Rule | Level | Fires when |
|---|---|---|
| UTC-001 Appearance correction | Warn | Appearance_Head == 0; the engine forces this to 1 at runtime. |
| UTC-002 Class limit | Warn | More than two entries in ClassList, which is more than a creature has slots for. Counts entries, and does not care whether they are distinct. |
| UTC-003 Class duplications | Error | The same class id appears twice in ClassList; fatal engine crash (0x5f7) on load. Fires once per repeat, so a list of three identical ids reports twice. |
| UTC-004 Dead save fields | Info | SaveWill or SaveFortitude populated; the engine reads willbonus and fortbonus instead. |
| UTC-005 Gender clamp | Warn | Gender > 4; the engine clamps to a maximum of 4. |
| UTC-006 GoodEvil clamp | Warn | GoodEvil > 100; the engine clamps to a maximum of 100. |
| UTC-007 Toolset and legacy fields | Info | Any of Comment, Morale*, PaletteID, BodyVariation, TextureVar, BlindSpot, MultiplierSet, NoPermDeath, IgnoreCrePath, Hologram, WillNotRender or LawfulChaotic is set; never read by the K1 engine. |
Range, 2DA and resref-existence rules requiring a LintContext, under
rakata_lint::rules::utc_range:
| Rule | Level | Fires when |
|---|---|---|
| UTC-008 Race bounds | Error | Race does not resolve to a row in racialtypes.2da; engine crash 0x5f4 on load. |
| UTC-009 Class bounds | Error | Any ClassList[].Class does not resolve to a row in classes.2da, or is negative; engine load failure. |
| UTC-010 Appearance bounds | Error | Appearance does not resolve to a row in appearance.2da; engine renders a missing model. |
| UTC-011 Portrait bounds | Error | PortraitId, when not the 0xFFFE “use string Portrait” sentinel, does not resolve to a row in portraits.2da. |
| UTC-012 Resref existence | Warn | 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. |
Open questions
- Is a third class entry ignored, or fatal? The two-slot structure under
ClassListis established. What the loader does with a third valid, distinct entry is not: no pass recorded here has walkedReadStatsFromGff’sClassListloop to find out. The0x5f7crashes we have confirmed are for duplicate and out-of-range ids, neither of which is a plain count problem. Tracked on #106. - The base
CSWSObjectlayer is untraced.CSWSObject::SaveObjectStateandLoadObjectState, andSaveListenDataandLoadListenData, run at the tail ofSaveCreatureandLoadCreature. We did not decompile them in this pass, so there may be a further class split at the base layer we have not reached. ReadItemsFromGff’s namespacing. We confirmedCSWSCreature::ReadScriptsFromGffis a genuine class member. We did not re-check whetherReadItemsFromGffis one or a free function.FeatList’s top-level presence flag. We could not resolve the top-level call site’s own presence-flag register with certainty from the decompiler output. The documented reading comes by analogy from the identical idiom in the per-levelFeatListinside the PCLvlStatListloop.SaveReflex’s override path is (provenance: inferred), by analogy withSaveWillandSaveFortitude. No string exists in the binary to trace it from directly.- Downstream use of
BodyBagandInterruptable. Both are genuinely read and stored. Whether combat or AI logic ever consumes the stored values was not traced. - Runtime behaviour of an out-of-range
Spellid.Spell = 299onpartymember.utcindexes past the end of thespells.2dastruct array. What actually happens depends on heap layout and cannot be determined from the load path alone. LoadCreature’s size, and the sizes ofReadScriptsFromGff,ReadItemsFromGffandReadSpellsFromGff, are not recorded. OnlyReadStatsFromGff’s 7835 B is.
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 |
|---|---|---|---|
MaxHitPoints | SHORT | writes it, never reads it back: the field-name string has exactly two cross-references in the binary, both writers, and zero readers anywhere on any path | not one constant; we substitute 0 |
HitPoints | SHORT | reads it | keeps 1 |
CurrentHitPoints | SHORT | reads it | not one constant; we substitute 0 |
ItemList[].Repos_PosX | WORD | never reads it: never read anywhere in the creature item-loading call graph; the only reader of either label is ReadContainerItemsFromGff, serving placeable and store containers, which no creature load reaches | not one constant; the field holds the absence |
ItemList[].Repos_Posy | WORD | never reads it: never read anywhere in the creature item-loading call graph; the only reader of either label is ReadContainerItemsFromGff, serving placeable and store containers, which no creature load reaches | not one constant; the field holds the absence |
ItemList[].Infinite | BYTE | never reads it: the Infinite label has exactly two cross-references in the binary, both inside CSWSStore::LoadStore and SaveStore, so no function in this type’s item-loading call graph reads it | not one constant; we substitute 0 |
ItemList[].Repos_PosY | WORD | never reads it: never read anywhere in the creature item-loading call graph; the only reader of either label is ReadContainerItemsFromGff, serving placeable and store containers, which no creature load reaches | not one constant; we substitute 0 |
Comment | CExoString | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute "" |
PaletteID | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
SaveWill | BYTE | never reads it: the template’s own save-throw fields are ignored; the engine computes saving throws from base plus ability modifier plus active effects and reads these never | not one constant; we substitute 0 |
SaveFortitude | BYTE | never reads it: the template’s own save-throw fields are ignored; the engine computes saving throws from base plus ability modifier plus active effects and reads these never | not one constant; we substitute 0 |
BodyVariation | BYTE | never reads it: the string exists in the binary but its only cross-references are the item loader and saver, so it is a real field name on a different format and no creature function reads it | not one constant; we substitute 0 |
TextureVar | BYTE | never reads it: the string exists in the binary but its only cross-references are the item loader and saver, so it is a real field name on a different format and no creature function reads it | not one constant; we substitute 0 |
Morale | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
MoraleRecovery | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
MoraleBreakpoint | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
BlindSpot | FLOAT | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0.0 |
MultiplierSet | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
NoPermDeath | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
IgnoreCrePath | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
Hologram | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
WillNotRender | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | not one constant; we substitute 0 |
LawfulChaotic | BYTE | never reads it: the field-name string does not exist anywhere in swkotor.exe, so no creature function can look it up; a binary-wide string-existence check settles this as never read rather than read and ignored | 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 |
|---|---|---|
FirstName | CExoLocString | stamps empty |
LastName | CExoLocString | stamps empty |
Description | CExoLocString | keeps empty |
IsPC | BYTE | NOT EXAMINED; we substitute 0 |
Tag | CExoString | keeps "" |
Conversation | CResRef | keeps "" |
Interruptable | BYTE | keeps 0 |
Age | INT | keeps 0 |
Gender | BYTE | keeps 0 |
StartingPackage | BYTE | keeps 0 |
Race | BYTE | NOT EXAMINED; we substitute 0 |
Subrace | CExoString | keeps "" |
SubraceIndex | BYTE | keeps 0 |
Deity | CExoString | keeps "" |
Str | BYTE | keeps 0 |
Dex | BYTE | keeps 0 |
Int | BYTE | keeps 0 |
Wis | BYTE | keeps 0 |
Con | BYTE | keeps 0 |
Cha | BYTE | keeps 0 |
NaturalAC | BYTE | keeps 0 |
SoundSetFile | WORD | stamps 65535 |
Gold | DWORD | keeps 0 |
Invulnerable | BYTE | keeps 0 |
Plot | BYTE | keeps 0 |
Min1HP | BYTE | keeps 0 |
PartyInteract | BYTE | keeps 0 |
NotReorienting | BYTE | keeps 0 |
Disarmable | BYTE | keeps 0 |
Experience | DWORD | keeps 0 |
PortraitId | WORD | stamps 65535 |
Portrait | CResRef | keeps "" |
GoodEvil | BYTE | keeps 0 |
Color_Skin | BYTE | keeps 0 |
Color_Hair | BYTE | keeps 0 |
Color_Tattoo1 | BYTE | keeps 0 |
Color_Tattoo2 | BYTE | keeps 0 |
Phenotype | INT | keeps 0 |
Appearance_Type | WORD | keeps 0 |
Appearance_Head | BYTE | keeps 0 |
DuplicatingHead | BYTE | keeps 0 |
UseBackupHead | BYTE | keeps 0 |
FactionID | WORD | keeps 0 |
ChallengeRating | FLOAT | keeps 0.0 |
AIState | INT | keeps 0 |
BodyBag | BYTE | keeps 0 |
PerceptionRange | BYTE | stamps 11 |
willbonus | SHORT | keeps 0 |
fortbonus | SHORT | keeps 0 |
refbonus | SHORT | keeps 0 |
ForcePoints | SHORT | keeps 0 |
CurrentForce | SHORT | not one constant; we substitute 0 |
SkillPoints | WORD | keeps 0 |
MovementRate | BYTE | not one constant; our reader works it out from other fields |
WalkRate | INT | not one constant; we substitute 0 |
ScriptHeartbeat | CResRef | keeps "default" |
ScriptOnNotice | CResRef | keeps "default" |
ScriptSpellAt | CResRef | keeps "default" |
ScriptAttacked | CResRef | keeps "default" |
ScriptDamaged | CResRef | keeps "default" |
ScriptDisturbed | CResRef | keeps "default" |
ScriptEndRound | CResRef | keeps "default" |
ScriptDialogue | CResRef | keeps "default" |
ScriptSpawn | CResRef | keeps "default" |
ScriptRested | CResRef | keeps "default" |
ScriptDeath | CResRef | keeps "default" |
ScriptUserDefine | CResRef | keeps "default" |
ScriptOnBlocked | CResRef | keeps "default" |
ScriptEndDialogu | CResRef | keeps "default" |
ClassList | List | NOT EXAMINED; we substitute container |
ClassList[].KnownList0 | List | not one constant; we substitute container |
ClassList[].KnownList0[].Spell | WORD | NOT EXAMINED; we substitute 0 |
ClassList[].KnownList0[].SpellFlags | BYTE | NOT EXAMINED; we substitute 0 |
ClassList[].KnownList0[].SpellMetaMagic | BYTE | NOT EXAMINED; we substitute 0 |
ClassList[].Class | INT | NOT EXAMINED; we substitute 0 |
ClassList[].ClassLevel | SHORT | NOT EXAMINED; we substitute 0 |
ClassList[].SpellsPerDayList | List | not one constant; we substitute container |
FeatList | List | NOT EXAMINED; we substitute container |
FeatList[].Feat (required) | WORD | NOT EXAMINED; we substitute 0 |
SkillList | List | not one constant; we substitute container |
SkillList[].Rank (required) | BYTE | NOT EXAMINED; we substitute 0 |
Equip_ItemList | List | NOT EXAMINED; we substitute container |
Equip_ItemList[].EquippedRes (required) | CResRef | not one constant; we substitute "" |
Equip_ItemList[].Dropable | BYTE | stamps 0 |
Equip_ItemList[].ObjectId | DWORD | stamps 2130706432 |
ItemList | List | not one constant; we substitute container |
ItemList[].InventoryRes (required) | CResRef | not one constant; we substitute "" |
ItemList[].Dropable | BYTE | stamps 0 |
ItemList[].ObjectId | DWORD | stamps 2130706432 |
SpecAbilityList | List | not one constant; we substitute container |
SpecAbilityList[].Spell (required) | WORD | NOT EXAMINED; we substitute 0 |
SpecAbilityList[].SpellFlags | BYTE | NOT EXAMINED; we substitute 0 |
SpecAbilityList[].SpellCasterLevel | BYTE | NOT EXAMINED; we substitute 0 |
TemplateResRef | CResRef | not one constant; we substitute "" |
CreatureSize | INT | stamps 3 |
IsDestroyable | BYTE | stamps 1 |
IsRaiseable | BYTE | stamps 1 |
DeadSelectable | BYTE | stamps 1 |
AmbientAnimState | BYTE | stamps 0 |
Animation | INT | stamps 10000 |
CreatnScrptFird | BYTE | stamps 0 |
PM_IsDisguised | BYTE | stamps 0 |
PM_Appearance | WORD | stamps 0 |
Listening | BYTE | stamps 0 |
AreaId | DWORD | stamps 0 |
DetectMode | BYTE | stamps 0 |
StealthMode | BYTE | stamps 0 |
LvlStatList | List | not one constant; we substitute container |