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

Fields Vanilla Writes That the Engine Never Reads

Vanilla resources are full of fields the shipped game does not look at. Not fields with unremarkable defaults, and not fields whose loader we simply have not traced yet: fields that carry real, deliberate-looking values in the retail data and reach no engine code path at all.

The fields are gathered here rather than one per format page, because a tool author asking whether it is safe to drop Comment on write is asking one question, not fourteen.

Why an unread field still matters

The obvious reading is “harmless leftover”, and for a round trip that is true. It stops being true the moment something reasons about the file’s meaning:

  • Editors. A field nobody reads still round-trips, so an editor that silently drops it produces diffs against vanilla for no behavioural reason and makes real changes harder to spot.
  • Linters. A rule that tells an author to set a field the engine ignores is worse than no rule. The distinction between “the engine reads this and you got it wrong” and “the engine has never once looked at this” is the whole value of the diagnostic.
  • Modders. Setting a dead field and expecting an effect is a trap that fails silently, which is the worst way for it to fail.

The standard of evidence

There are two very different claims available here, and they are worth keeping apart.

The weak claim is “the loader we traced does not read it”. That is always provisional. Tracing covers the paths somebody walked, and a field could be read somewhere nobody looked.

The strong claim is not “we did not find a reader”, it is “no reader can exist”. There are two ways to earn it, and they fail in opposite situations, so it is worth knowing both.

By name: the label appears nowhere in the executable

The engine’s GFF readers take the field name as a literal string argument, so a label that appears nowhere in the binary cannot be handed to a reader on any code path. This is the route most of this page takes.

A string search that finds nothing is only as good as its ability to find something, so the searches behind this page are checked against labels that are read. Absence of NumWords means something because the same search does turn up VO_ResRef beside it.

Where it fails: when the name exists for some other reason. A field the engine writes, or syncs, or reads under a different concept, puts its own label in the binary and the search comes back inconclusive.

By reach: nothing can get to the value

Trace outward from the value instead of inward from the name. Enumerate every function that can hand out a pointer to the structure holding it, then enumerate their callers. If that set closes and no member reads the field, no reader can exist, whatever the name search says.

The journal’s Picture is the worked example, and it is exactly the case the name test cannot settle. The engine reads the field, keeps it, and syncs it from the server side to the client through a handler of its own, so the label is unmissable in the binary. But only two functions in the whole executable can return a journal entry pointer, and between them they have a single caller, which reads four other fields and not this one. See JRL.

Where it fails: when the accessors are numerous, or reached through a virtual call or a function-pointer table, so the set never closes. The name test does not care how a reader would have been reached, which is why the two complement each other rather than ranking.

A field is worth the strong claim under either route. Say which one you used, because the next reader’s field may only be reachable by the other.

DLG: NumWords, VO_ID, IsChild, Comment, LinkComment

A scan of every .dlg in chitin.key and the module RIM archives turns up five fields present and carrying real values that no loader function ever reads. This isn’t inferred from tracing every call site. It is confirmed directly: CResGFF::ReadField* always takes the field’s name as a literal string argument, and none of these five label strings, NumWords, VO_ID, IsChild, Comment, LinkComment, exist anywhere in the compiled binary at all (verified against a working string search that does find neighboring labels like VO_ResRef and DisplayInactive). A field whose name string doesn’t exist in the binary cannot be read by any code path, so this is a stronger result than “untraced”: these fields are structurally inert at runtime, full stop.

Because none of the five is ever handed to a reader, the usual “what happens when the field is absent” question doesn’t apply to them the way it does to fields the engine actually reads: there’s no default-substitution logic and no carried-over-value behaviour to describe, because there’s no reader code path to hit in the first place. Presence or absence in the file makes no difference to engine state.

Measured over every .dlg in chitin.key and the module RIM archives, which is where all of them live; the static ERFs and the save corpus contain none. Prevalence is given per distinct resref, since a handful of dialogues ship in more than one archive.

FieldStructCarried by
NumWordsEntries and repliesEvery dialogue, without exception
CommentNodesEvery dialogue, without exception
VO_IDNodesEvery dialogue
IsChildLinksAbout nine in ten
LinkCommentLinksAbout half

NumWords and Comment are on every single file rather than “most”: a reader will meet them universally, and a writer that drops them produces something no shipped dialogue resembles.

What each field appears to be, going by name and by the patterns already documented for other formats in this codebase:

  • NumWords: reads like toolset-computed word-count metadata (translation/VO-scheduling bookkeeping), never read back by the engine. No word-counting logic over Text exists anywhere in the binary either, so it isn’t silently recomputed and re-verified on load: it is pure dead round-trip data.
  • VO_ID: reads like an authoring-side lookup key into an external voice-over production database, distinct from the engine’s own VO_ResRef playback resref. Never consumed by the runtime.
  • Comment: the same dead-authoring-metadata pattern already documented for UTC and UTD’s Comment fields, just more absolute here: those formats at least read the field into an unused struct member, where DLG’s loader doesn’t reference the string at all.
  • IsChild and LinkComment: set-equal, the strongest correlation in this document. See below.

IsChild and LinkComment are set-equal

LinkComment is present in exactly the dialogues where IsChild is non-zero, checked in both directions, with no dialogue carrying one without the other, in every population and whether counted per file or per distinct resref.

The non-zero part is load-bearing. IsChild is present far more often than it is non-zero, and the several hundred dialogues carrying an IsChild of 0 carry no LinkComment either. Stated against presence, the correlation would be false.

It suggests the two are one editor-side subsystem rather than independent fields. That cannot be confirmed or refuted from the compiled engine, because the loader never parses either field on any struct: not the node, not the link entry, nowhere. Whatever relationship they have belongs to the original toolset’s authoring format, so settling it would mean finding that toolset rather than reading more of the game.

None of the five is modelled by rakata_generics::Dlg, which is the projection rule working as intended: a typed view carries what the engine reads at that path, so a field with no reader has nothing to project. The consequence for tooling is that a Rakata round trip drops all five.

So: is it safe to drop them? For the engine, yes, without qualification. These are not fixed-position bytes and nothing reads them, so a file without them loads identically to one with them. That is the question this page opened by naming, and it has an answer.

For anything else, no. Dropping them produces a diff against vanilla on every dialogue in the game, which makes real changes harder to spot in a review and harder to find in a bug report. NumWords and Comment are on every shipped dialogue without exception, so a file lacking them resembles nothing BioWare emitted. And an authoring tool that reads them, which is what they exist for, loses whatever its user put there.

The recommendation is therefore to preserve them on round trip and not to invent them on creation. Rakata does the second and not the first, which is a limitation of the typed view rather than a judgement that the fields do not matter.

UTC: Tail and Wings

ReadStatsFromGff carries no read for either label. It assigns 0 to both members flat and unconditionally, overwriting whatever the object held and whatever the file said. Both the blueprint path and the save-instance path call that same function, and nothing in it distinguishes its callers, so this is not a template-only or save-only behaviour.

The strong claim here comes from the overwrite rather than from the absence of a reader. Even if some other subsystem read a creature’s tail or wings, it would read the 0 the loader just wrote. The file’s value cannot reach anything, whatever else in the binary touches those members.

SaveStats writes both on every save regardless, so a save file’s tail and wings are discarded the moment they load back.

Safe to drop? For the engine, yes. Dropping them on a .utc blueprint costs nothing at all, since the loader was going to zero them anyway.

For a save writer aiming to match what the engine emits, write them as 0, because the engine does. This is the one place the two answers come apart: “the engine ignores it” and “vanilla omits it” are different claims, and here only the first holds.

UTW: TemplateResRef

LoadWaypoint never reads TemplateResRef, not even as a discarded read that advances past the value. LoadFromTemplate is the only other candidate and it matters only for a waypoint a script spawns at runtime, where the resref arrives as the script’s own CreateObject() argument rather than from the field. The label is never looked up anywhere in waypoint loading.

A corpus pass over a full install found it on every waypoint. It is what the toolset writes each time and the engine reads never, which makes “legacy padding” too generous a description.

Safe to drop? For the engine, yes. Waypoints resolve no template at all, so unlike a door or a placeable there is not even a blueprint the value could have pointed at, and nothing downstream loses a fallback.

Against vanilla, dropping it puts a diff on every waypoint in the game. Preserve it on round trip; there is nothing to invent it from on creation, since no blueprint exists to name.

GIT: Tag on a templated placement

This is the page’s one conditional entry, and the condition is what makes it useful. Every templated GIT object type reads Tag unconditionally from whichever struct the loader is handed: the placed instance’s own struct on a direct load, and the blueprint’s top-level struct on a template load. The area-level dispatcher’s post-template overlay covers position, orientation and geometry, and never Tag.

So a placement’s own Tag is dead exactly when that placement is templated, and live when it is not. Confirmed across Placeable, Trigger, Sound, Store, Encounter, Creature and Item as well as Door, so it is general engine behaviour rather than a door-specific gap. See GIT.

Every door placement in a full install carries a Tag holding a real, non-empty value. It is not stale padding on a few assets.

Safe to drop? This one you should keep, and it has the highest cost of dropping on this page.

The instance Tag is the only per-placement identifier anything outside the engine has. A GIT full of placements distinguished solely by position is far worse to diff, to review and to write a lint rule against. Worse, because the engine takes the blueprint’s Tag instead, two placements sharing one blueprint end up sharing one tag at runtime, with no engine-side protection anywhere; vanilla avoids that collision purely by authoring one blueprint per placed instance. A tool that drops the instance Tag removes the only record of what the author meant each placement to be.

  • DLG Format for the rest of the dialogue format, including the fields the engine very much does read.
  • Testing for why “no vanilla file exercises this” is a statement about the corpus and not about the engine.