1. What the graph file contains
A graph<taskId>.dat file supplies an AI navigation graph to an
AIGraph task. The task id appears in the filename and the graph's node positions are stored
in the graph task's local frame. The engine adds the task position to a node position when it needs world
coordinates.
The research signatures and method addresses below come from the IGI 1 graph analysis by Dimon Krevedko, Yoejin Light and Artiom Rotari, continued by IGI ResearchDevs@HM. The stream behavior and corpus observations are cross-checked against the OpenIGI graph reader and its retail corpus notes.
2. Two on-disk variants
| Variant | How it is recognized | Contents | Retail corpus |
|---|---|---|---|
| Named fields | Begins with bytes CC DD EE FF (little-endian value 0xFFEEDDCC). |
Typed GraphStream values addressed by field-name hashes. Common order: capacity, route table, nodes, then links. | 293 of 300 files |
| Positional | No leading magic; the loader rewinds and reads fixed fields. | Node count, slot count and link count, followed by node data, links and route table. Nodes also carry two counted pair lists. | 7 of 300 files; all are from level 8 |
The field order is consistent in the named retail files, but these are streams rather than fixed-size C records: each field is written as values and node/link records are assembled as their fields arrive. The engine commits a node when its criteria field arrives and a link when its link-type field arrives.
3. Named-field signatures
The field name is not stored as text. The engine hashes its registered name, stores the hash as a typed 32-bit value, then reads the declared value type and payload. The abbreviated signature below is the four-byte prefix shown in the original static research: a type tag followed by the low three bytes of the little-endian field hash. The full hash is included so fields that share a prefix remain distinguishable.
| Field | Field hash | Signature prefix | Value type |
|---|---|---|---|
nMaxNodes | 0x000D3AE6 | 04 E6 3A 0D | Int32 |
nNodeID | 0x000735CE | 04 CE 35 07 | Int32 |
tNodePosition | 0x001D4295 | 04 95 42 1D | Real64x3 (24 bytes) |
vNodeGamma | 0x000F7E9C | 04 9C 7E 0F | Real32 |
vNodeRadius | 0x00143023 | 04 23 30 14 | Real32 |
nNodeMaterial | 0x001BB629 | 04 29 B6 1B | Int32 |
nNodeCriteria | 0x001BD3E5 | 04 E5 D3 1B | String8 |
nLinkID1 | 0x0009104A | 04 4A 10 09 | Int32 |
nLinkID2 | 0x000918F6 | 04 F6 18 09 | Int32 |
eLinkType | 0x000DA923 | 04 23 A9 0D | Int32 |
RouteTable | 0x000FDE0D | 04 0D DE 0F | Blob |
A string value carries a length byte; the count includes the terminating NUL. A blob carries its element type and declared length before its bytes. The route-table reader uses the graph's declared slot count to determine the expected table size.
4. Nodes, links and positional-only data
4.1 Node fields
| Field | On-disk meaning | Observed engine behavior |
|---|---|---|
nNodeID | Integer node id; ids are also slot indices. | Retail ids start at 1. Slot 0 is unused; the largest observed id is 380. |
tNodePosition | Three little-endian 64-bit floats. | Stored relative to the graph task. The engine adds the task position for world coordinates. |
vNodeGamma | 32-bit float. | Preserved by the parser; its gameplay meaning is not established here. |
vNodeRadius | 32-bit float. | The nearest-node query scales it by 4096 engine units per metre. |
nNodeMaterial | Integer material id. | -1 represents no material on the named-field path. |
nNodeCriteria | Length-prefixed string. | Names resolve to registered criteria values; the empty string is an ordinary node. |
The static notes call the criteria field Stringx18; the stream reader handles the named form as a
length-prefixed string. Treat 18 as the research's field label, not a proven fixed on-disk width.
4.2 Criteria names
| Stored name | Resolved value | Observed use |
|---|---|---|
NODECRITERIA_DOOR | 1 | Doorway node |
NODECRITERIA_VIEW | 2 | Observation post; excluded by the nearest-node query |
NODECRITERIA_STAIR | 4 | Stair node |
These are names resolved to bits in registration order, not a packed multi-bit integer in the file. Retail files contain the empty string or one of these three names.
4.3 Links
A link is assembled from nLinkID1, nLinkID2 and eLinkType. The engine
registers it with both endpoint nodes, so links are undirected in the graph API. All 24,433
links observed in the retail corpus have link type 1.
4.4 Positional-only node data
The seven files without the magic use positional records. Each begins with node count, slot count and link count. A node then carries its id, position, gamma and radius, followed by an optional float, an optional material id, a criteria string and two counted lists of integer pairs. The engine's AIGraph hook reads the two lists to advance the stream; the retail parser retains them as positional extras. Links follow the nodes, and the route table follows the links when present.
5. Route table
The precomputed route table has nMaxNodes × nMaxNodes entries, each 8 bytes:
a little-endian signed 32-bit next-node id and a 32-bit float cost. Its total size is therefore
8 × nMaxNodes² bytes.
struct RouteEntry {
int32_t next_node; // -1 means no route
float cost;
};
For a route from from to to, the entry index is
from + to × nMaxNodes. A no-route entry is { -1, -1.0 }. The engine asks the
table for the next node at each step; the file already contains the route choices. This describes the
observed lookup and does not assume which shortest-path algorithm produced the table.
6. Runtime extraction methods
The original research identifies these retail IGI.EXE routines as field extractors. Addresses
are image-specific and should be read as labels for the analyzed executable:
| Field | Routine |
|---|---|
nMaxNodes | SUB_004FAEE0() |
nNodeID | SUB_004FAF50() |
tNodePosition | SUB_004FAF80() |
vNodeGamma | SUB_004FAFA0() |
vNodeRadius | SUB_004FAFC0() |
nNodeMaterial | SUB_004FAFE0() |
nNodeCriteria | SUB_004FB030() |
nLinkID1 | SUB_004FB080() |
nLinkID2 | SUB_004FB0B0() |
eLinkType | SUB_004FB0D0() |
7. Retail corpus observations
- 300 graph files across the fourteen levels: 293 named-field and 7 positional.
- Declared slot counts: 100 in 274 files, 200 in 19, 300 in 4 and 400 in 3.
- 8,317 nodes; the largest node id is 380. Slot 0 is unused.
- 24,433 links; 203 files contain links and 90 contain none.
- All route tables are square over the declared slot count and are consumed exactly at end of file.
- Criteria counts: empty 7,022;
NODECRITERIA_DOOR465;NODECRITERIA_VIEW161;NODECRITERIA_STAIR669.
Evidence note. The corpus counts and parser behavior above describe the OpenIGI retail-data audit. They are implementation cross-checks; the original graph research remains the source for the static signatures, criteria names and runtime addresses credited above.