Placement files (.pgeo)
Nearly everything placed by rule in the world (buildings, props, trees, bushes, barriers, rocks, signs, groundcover,
infrastructure, bridges, smashables and the crowd and spline templates) is stored in .pgeo placement files, one file
per (cell size, cell, category) under scene\proc\cellsize\<cs>\<cx>_<cz>\. A file is a list of named blocks, each a
template name followed by its instances; the template name maps to one model through the category’s template
library. :observed In each season a file is shown in, a block name is the name
of exactly one model template of its category’s library for 65,988,966 of the 66,029,611 instance-seasons (instances
counted once per season they are shown in). The rest: 37,066 name no model template (27 names, all the winter groundcover
GRASSTEMPLATE crops 3D_CROP_LAVENDER_*, 3D_CROP_ONION_SUM_*, 3D_CROP_SUNFLOWER*, 3D_GRASS_TUFTY_* and
GEN_LONG_GRASS_*, which the winter library does not define) and 3,579 name two (bar_gbl_crct_foam_01_b_3D in every
season, tre_urb_ginkgo_d_3D in autumn). Proven
Following those templates’ model paths, 65,058,898 of the 65,996,124 (instance, season, model path) triples name a model
whose .modelbin is among the track’s GeoChunk entries and 937,226 name none. Proven
The Tokyo track Brio has 96,234 .pgeo files, all in GeoChunk0, holding 665,298 blocks and 29,164,671 instance records
(the four season variants of a position count separately). Proven Counts below are of that census unless a sentence says
otherwise; counts not restated here come from an earlier census of the same track. Observed
Files and names
Section titled “Files and names”The file name is c<cs>_<category>_<lod>[_rt_<route>][__tag_<tag>_ | _<gameplay object id>][_<mask>].pgeo. Cell sizes on
this map are 50, 75, 100, 125, 150, 200, 250, 300, 500 and 10,000 m (3,489, 20,998, 37,048, 21,157, 1,821, 5,467, 2,025,
3,849, 378 and 2 files). Proven A cell’s world origin is (cx × cs, 0, cz × cs). Observed Categories are buildings,
props, trees, bushes, barriers, rocks, signs, grasstemplate, infrastructure, bridges, smashables, crowdtemplate, cwdspline
and evolving_world. Observed The header’s category names one of 13 libraries: barriers 3,764
files, bridges 198, buildings 3,495, bushes 35,439, crowdtemplate 540, evolving world 9, grasstemplate 20,998,
infrastructure 683, props 4,444, rocks 1,418, signs 1,182, smashables 2,529, trees 21,535. Proven
| Suffix | Holds | Confidence |
|---|---|---|
_do |
full-detail placements, except vegetation (_trees_do in the Tokyo core is plant pots) |
Observed |
_ulc |
the real trees and bushes: <template>_3D blocks plus <template>_impostor blocks at the same positions |
Observed |
_ulp |
impostors only | Observed |
_so |
vista impostors, far billboards, bush collision proxies | Observed |
_rt_<route>, __tag_<tag>_, _<gameplay object id> |
gameplay-gated content | Observed |
_<N> (119, 187, 221, 238 …) |
season variants: the season-exclusion mask as a number | Observed |
grasstemplate_do (cell size 75) holds groundcover and crops: blocks named <template>_3D_HIGH, 8,454,531 records in
36,087 blocks map-wide (49 distinct templates: susuki, Aleutian avens, dichondra, tufty grass, cabbage, onion, lavender,
sunflower), about 3.4 million of them in summer. _3D_HIGH is the only _3D_<suffix> form in any .pgeo category;
grasstemplate_so holds 8.5 million far billboard copies (3D_ASSET_BILLBOARD*_TEMPLATE_HIGH) and 7 stray
GEN_LONG_GRASS_*_3D_HIGH. The rice-paddy templates exist in the library but nothing places them through .pgeo.
Observed
File grammar
Section titled “File grammar”One grammar covers all 96,234 files, and walking it consumes every byte of every file, with every constant below holding
on all of them. Proven All strings are u32 length plus printable ASCII with no terminator. Proven
Little-endian. The end of the file follows the last list; kind 5 files add a spline section.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 + n | str | section name | Observed | like c75_grasstemplate_do_221_cellx55z114_section0 |
| next | 4 | u32 | kind | Proven | 0 = ordinary category file (95,694), 2 = crowdtemplate_cwd (296), 5 = cwdspline (244); no other value |
| next | 4 | u32 = 3 | three | Proven | kind 5 only |
| next | 8 | u32[2] = 13, 15 | constants | Proven | |
| next | 32 | f32[8] | bounding box | Proven | minX minY minZ 0, maxX maxY maxZ 1; w is 0 and 1 on every file; every instance position and every crowd spline box lies inside it, widened by one fixed-point step and one f32 rounding |
| next | 4 + n | str | category | Observed | the library category: PROPS, GRASSTEMPLATE, CROWDTEMPLATE, EVOLVING_WORLD ... |
| next | 4 | f32 | unknown | Unknown | 0.59 to 23 m, per file |
| next | 4 + n | str | route | Observed | empty = none; "0", "8003"; crowd: "~101", "~351;~8008" |
| next | 4 + n | str | gameplay tag | Observed | empty = none; [DRIFT_ZONE_17], BARN_FIND_MAZ_55_787B_91, TAXIJOB |
| next | 4 | u8, u8[3] | season exclusion | Proven | mask byte (its two nibbles equal on all 96,234 files) then three zero bytes; see Seasons below |
| next | 4 | u32 = 0 | zero | Proven | |
| next | 4 + 32 n | table entry[] | table | Proven | |
| next | variable | block[] | list A | Proven | u32 count, then blocks |
| next | variable | block[] | list B | Proven | u32 count, then blocks |
| next | 4 | u32 = 0 | zero | Proven | |
| next | variable | crowd spline[] | crowd splines | Proven | kind 5 only: u32 count (1 on all 244 files), then the splines; see Crowd spline below |
Each block is str name; u32 n; n × 80-byte instance record; u32 m (0 or n); m × 96-byte curve record; u32 nSources; nSources × { str source template, u32 first, u32 count }. Proven The sources are contiguous from record 0 and their counts
sum to n; nSources is 0 on exactly 1,416 blocks: 315 PEDESTRIAN, 273 ANIMATED and 273 ANIMATED_SPRITE crowd blocks and 555
bsh_flat_urb_garden_3D blocks; the other blocks hold 739,181 source ranges in all. Proven The source is the
placement template the records came from (for grass, GBL_SUSUKI_SUM_A_HIGH, defined further down the library): every
source range names a placement template of the category’s library in every season the file is shown in (2,748,074 range
and season pairs, none unresolved). Proven
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 = 0 | zero | Observed | |
| 0x4 | 4 | u32 | hash | Observed | one value per template (554 distinct, 1:1 with the block name); the function is not CRC-32, FNV, djb2 or sdbm of the name, stem or path |
| 0x8 | 12 | i32, u32, u32 | B range | Observed | block index, first record, count in list B; -1, 0, 0 when none |
| 0x14 | 12 | i32, u32, u32 | A range | Observed | block index, first record, count in list A; -1, 0, 0 when none |
The first triple is list B and the second list A on all 189,514 entries; _do entries have only the B triple,
smashables_so entries only the A triple, _ulc entries both with equal values. Every range lies inside its block, and
188,987 entries are exactly one source range of the block (the other 527 cover the whole block). Observed Which blocks get
entries is not a template property (376 source templates are covered in some cells and not in others) and correlates with
no record field; the entries carry no placements. Unknown
The two block lists
Section titled “The two block lists”| File | List A | List B | Confidence |
|---|---|---|---|
_do (every category) |
empty | the model blocks <template>_3D (grass _3D_HIGH) |
Observed |
_ulc (trees, bushes) |
<template>_impostor, tre_lmt_*_impostor_vista, bsh_urb_hedge_*_impostor_N |
<template>_3D |
Observed |
_ulp |
impostor blocks | empty | Observed |
_so |
impostor and vista blocks; grass billboards | empty | Observed |
smashables_so |
Bush_Flattenable_*_3D collision proxies (17,994) |
empty | Observed |
crowdtemplate_cwd (kind 2) |
ANIMATED_SPRITE or empty |
ANIMATED, PEDESTRIAN |
Observed |
cwdspline (kind 5) |
empty | one PEDESTRIAN block |
Observed |
In every _ulc file list A and list B are parallel: same block count, same record counts, byte-identical 80-byte
records and identical sources (12,617 bush and 9,008 tree files); only the names differ. List A is the far copy, list B
the 3D models. Observed 4,034 tree files pair tre_gbl_elm_a_impostor with tre_lmt_elm_a_3D. Observed
The 80-byte instance record
Section titled “The 80-byte instance record”Little-endian. Coordinates are sign-magnitude fixed point, not two's complement.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 12 | u32[3] | position | Observed | x, y, z: bit 31 sign, bits 16-30 integer, bits 0-15 fraction / 65535 (the instanced shaders divide by 65535, not 65536) |
| 0xC | 24 | f32[6] | rotation rows | Observed | the first two rows of the 3×3 transform, unit vectors; the third is their cross product |
| 0x24 | 12 | f32[3] | scale | Observed | local axis scale; uniform in practice |
| 0x30 | 4 | u32 | flags | Unknown | 0x80800000 on all 29,164,671 records |
| 0x34 | 1 | u8 | instance variation x | Observed | the swatch-tint palette row (byte / 255), shared by all pieces of a building |
| 0x35 | 1 | u8 | instance variation y | Observed | per-piece value: lights on/off variation, and the tide-mark height of rock shaders |
| 0x36 | 1 | u8 | instance variation z | Observed | window_linecastroom room centre override |
| 0x37 | 1 | u8 | instance variation w | Observed | window_linecastroom room width override |
| 0x38 | 4 | u8[4] = 0 | zero | Proven | 0 on all 29,164,671 records |
| 0x3C | 1 | u8 | material variant | Observed | row of the model's variant table; 0 = base |
| 0x3D | 1 | u8 = 0 | zero | Proven | 0 on all 29,164,671 records |
| 0x3E | 2 | u16 | building id | Observed | index into the ModulateConstants frame table of the .pvs; 0 = no frame; shared by every piece of a building and its attached props, barriers and signs |
| 0x40 | 4 | u32 | unknown | Unknown | not described |
| 0x44 | 4 | u32 | random value | Observed | per-piece |
| 0x48 | 4 | u32 = 0 | zero | Proven | 0 on all 29,164,671 records |
| 0x4C | 4 | u32 | record tail | Unknown | 0x00000200 on most records; groundcover 0x100 or 0x200 (0x100 in the winter grass file) |
The instance variation bytes come from reading the compiled instanced vertex shaders (environment, uber_rock,
uber_rock_array): they load the record’s dword at +0x34 and output its four bytes as INSTANCE_VARIATION.xyzw = byte × float32(1/255).
The window shader reads k = sat(IV.w × 25.6), RS.x = lerp(RS.x, IV.w × 25.6, k), RP.x = lerp(RP.x, IV.z × 25.6 − 12.8, k).
Observed
The GPU instance buffer the shaders read (a StructuredBuffer of stride 64) has the same layout for dwords 0 to 13;
dwords 14 and 15 are packed by the CPU (a raw dword 14 would cull every instance; dword 15 bits 8 to 23 hold the frame
index, the record’s u16 at +0x3E). Observed Groundcover records use the same 80 bytes with a uniform scale, flags
0x80800000, variation bytes FF FF FF FF (tint row 1.0), variant 0 and a random value. Nothing in a record selects among
models: a template has one model, and variety comes from separate templates. Observed
Every position of the 29,164,671 records, read as fixed point on all three axes, lies inside its file’s header box widened
by one fixed-point step (1/65535 m) and one f32 rounding. Proven The box bounds
the positions; the cell does not: 337,778 records lie outside the cell whose folder holds their file (the other 28.8
million inside), so a cell names where a file is kept. Proven The sign-magnitude form matters: 0x96F33B00 is −5875.23, not −26893. Observed Groundcover sits on the extracted terrain
mesh: susuki in x 3434..3690, z 4784..5040 (160,671 summer records, 30,000 sampled) at y − terrain median 0.000 m, p99
0.006 m; crops in x −1450..−1194, z −3900..−3644 (6,437) median −0.015 m, p99 0.13 m. Observed
Only 12 of 14.4 million placements have a scale below 0.1 (a row of bsh_urb_garden_* at 0.021 near (−2977, −5462), one
maple at 0.09, one wall post at 0.05), all at ground height; no record marks a placement as disabled. Observed
Rotation convention
Section titled “Rotation convention”The engine is Direct3D row-vector: p' = p · M. The stored r0 and r1 are the first two rows of M and
row2 = cross(row0, row1). Treating them as basis vectors rotates every yawed instance by the inverse yaw (modular
wall and sleeper pieces scatter into slabs). Observed
Curve records
Section titled “Curve records”A block has no curve records or one per instance. Proven They are present on 11,658 blocks, holding 255,028 records:
the “no curve” sentinel (−990, −990, −990, −991 × 3, ..., −997 × 3) in 11,634 of them and, in the other 243,394, the
instance’s spline data, the struct the SplineDeform vertex shaders read verbatim. Proven The blocks are barriers 11,367,
infrastructure 168, props 114, bushes 6, rocks 2 and smashables 1 (218 block names). Observed
24 floats: two cubic polynomials, P(t) = c0 + c1 t + c2 t² + c3 t³ and Up(t) = c4 + c5 t + c6 t² + c7 t³.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 48 | f32[3][4] | curve c0..c3 | Observed | world metres; c0 is the instance position (the record's fixed point is coarser, within 0.000122 m) |
| 0x30 | 48 | f32[3][4] | up vector c4..c7 | Observed | its length is the y scale |
The record’s rigid transform is derived from the curve: c4 / |c4| equals the second rotation row and |c4| the uniform
scale on every curved record (within 3e-6); the first row is the chord direction normalize(P(1) − P(0)) on 218,297 of
the 237,572 curved _barriers_do records (and the tangent normalize(c1) on 101,744, where the piece is straight).
P(1) is, on a 1 cm grid, the P(0) of another record of the same block for 167,137 of the 243,394, so the pieces form
chains. Observed Some records of templates that never bend are wild (planters: bends up to 279 m). Observed How the bend
is applied is on Spline deform.
Crowd spline (kind 5). After the lists: u32 nSplines (1 on all 244), then per spline a GUID string, an 8-float box, a
length, a float (0.72 to 1), u32 nPoints, and nPoints × 80 bytes (three vec4 with w = 0 whose roles are not verified,
eight cumulative distance samples), then two lists of floats whose meaning is unknown; the layout reads all 244 files to
their last byte. Proven The PEDESTRIAN records of these
files sit at (0, 0, 0): all 2,914 records of the 244 files. Proven The name grammar is c300_cwdspline_<cx>_<cz>_<n> under cell 0_0; the route carried
by 37 of them is in the header only. Observed
Gameplay gating
Section titled “Gameplay gating”The name grammar is also stored in the header, and on all 95,990 non-cwdspline files the name rebuilt from (cell size,
category, route, tag, mask) equals the file name: mask is the numeric suffix, tag [X] is __tag_x_, other tags are _x,
routes are _rt_<route> with ~ written x and ; written _. Observed
| File | Route | Gameplay tag | Mask |
|---|---|---|---|
c50_buildings_do.pgeo |
0x00 | ||
c200_props_do_rt_0.pgeo (traffic cones, sandbags) |
0 |
0x00 | |
c50_buildings_do_rt_8003.pgeo |
8003 |
0x00 | |
c125_trees_rt_1_ulc.pgeo |
1 |
0x00 | |
c200_props_do__tag_drift_zone_17_.pgeo |
[DRIFT_ZONE_17] |
0x00 | |
c200_props_do_rt_0_discount_board_xp_a_099.pgeo |
0 |
DISCOUNT_BOARD_XP_A_099 |
0x00 |
c50_buildings_do_barn_find_maz_55_787b_91_119.pgeo |
BARN_FIND_MAZ_55_787B_91 |
0x77 | |
c150_bridges_do__tag_bridge_pillars_.pgeo |
[BRIDGE_PILLARS] |
0x00 | |
c300_signs_do_taxijob.pgeo |
TAXIJOB |
0x00 | |
c50_buildings_do_119.pgeo (bld_gbl_resi_04_a_snow) |
0x77 |
All rows Observed. The tag names are gameplay-object ids (ANIM_CM_* match AnimatedCollisionMeshes.xml, barn finds,
speed cameras, estate entrances) or bracketed group tags. Observed Route 0 covers speed cameras, XP boards, danger signs,
mascots, trailblazer markers and traffic cones. Observed evolving_world_do files have no route or tag. Observed
No field in the .pgeo header, the hand-placed instance record, the executable’s strings or GameObjs.xml gives a default
state. Stripped\EntityModel.zip and the encrypted game database were not searched.
Seasons
Section titled “Seasons”The season byte uses the trigger-zone bits of FORZA_SeasonExclusion: the low nibble’s bit 0 is spring, 1 summer, 2
autumn, 3 winter, and a set bit means the content is not shown in that season; the high nibble repeats the low one on
every one of the 96,234 headers. Proven
| Suffix (mask) | Shown in | Content (record counts, whole map) | Confidence |
|---|---|---|---|
| none (0) | all | most files of every category; grass: crops and tufty grass (37,000) | Observed |
_221 (0xDD) |
summer | grass 3.41 M: Aleutian avens, SUSUKI_SUM, dichondra |
Observed |
_187 (0xBB) |
autumn | grass 2.02 M: *_AUTUMN avens/dichondra, susuki from GBL_SUSUKI_ATM_* |
Observed |
_238 (0xEE) |
spring | grass 1.76 M: ALEUTIAN_AVENS_SPRING |
Observed |
_119 (0x77) |
winter | grass 1.20 M SUSUKI_WNT; buildings 3,298 *_snow roof overlays, *_snow barriers, snow log piles |
Observed |
_136 (0x88) |
not winter | crops (cabbage, onion, lavender) 22,000; a few props | Observed |
_204 0xCC / _153 0x99 / _51 0x33 |
spring+summer / summer+autumn / autumn+winter | a few dozen festival props, boxes | Observed |
_17 (0x11) |
not spring | one file, two records | Observed |
The install holds ten masks: 0 (75,018 files, 12,258,944 records), 119 (3,151; 2,412,979), 136 (577; 43,852), 153 (10; 328), 17 (1; 2), 187 (5,933; 4,055,622), 204 (7; 50), 221 (7,201; 6,843,752), 238 (4,331; 3,549,120) and 51 (5; 22); no other value occurs. Proven
The seasonal files repeat positions: in one 75 m cell (47, 59) all 2,162 SUSUKI_WNT_A positions are also SUSUKI_SUM_A
positions in _221 and again in _187; a winter bld_gbl_resi_04_a_snow sits exactly on its bld_gbl_resi_04_a.
Reading every file therefore stacks up to four copies. Observed Within one season the same prototype is also placed
twice at one transform: of the 82,026 summer placements of the Shibuya box, 274 transforms hold two placements of one
prototype (548 placements). Most are tree and bush impostors (tre_gbl_deciduous_e_impostor at 100 transforms,
bsh_gbl_elm_b_impostor at 59); the rest are building pieces such as the *_billbrd_01_a_3D billboard bases and tops. Observed See Seasons for what
else changes by season.