Skip to content

Placement files (.pgeo)

ForzaTechWorld and streamingApplies toFH6

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

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

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

.pgeo filevariableFH6

Little-endian. The end of the file follows the last list; kind 5 files add a spline section.

ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x04 + nstrsection nameObservedlike c75_grasstemplate_do_221_cellx55z114_section0
next4u32kindProven0 = ordinary category file (95,694), 2 = crowdtemplate_cwd (296), 5 = cwdspline (244); no other value
next4u32 = 3threeProvenkind 5 only
next8u32[2] = 13, 15constantsProven
next32f32[8]bounding boxProvenminX 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
next4 + nstrcategoryObservedthe library category: PROPS, GRASSTEMPLATE, CROWDTEMPLATE, EVOLVING_WORLD ...
next4f32unknownUnknown0.59 to 23 m, per file
next4 + nstrrouteObservedempty = none; "0", "8003"; crowd: "~101", "~351;~8008"
next4 + nstrgameplay tagObservedempty = none; [DRIFT_ZONE_17], BARN_FIND_MAZ_55_787B_91, TAXIJOB
next4u8, u8[3]season exclusionProvenmask byte (its two nibbles equal on all 96,234 files) then three zero bytes; see Seasons below
next4u32 = 0zeroProven
next4 + 32 ntable entry[]tableProven
nextvariableblock[]list AProvenu32 count, then blocks
nextvariableblock[]list BProvenu32 count, then blocks
next4u32 = 0zeroProven
nextvariablecrowd spline[]crowd splinesProvenkind 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

.pgeo table entry32 bytesFH6100% known
ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x04u32 = 0zeroObserved
0x44u32hashObservedone 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
0x812i32, u32, u32B rangeObservedblock index, first record, count in list B; -1, 0, 0 when none
0x1412i32, u32, u32A rangeObservedblock 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

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

Instance record80 bytesFH685% known

Little-endian. Coordinates are sign-magnitude fixed point, not two's complement.

ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x012u32[3]positionObservedx, y, z: bit 31 sign, bits 16-30 integer, bits 0-15 fraction / 65535 (the instanced shaders divide by 65535, not 65536)
0xC24f32[6]rotation rowsObservedthe first two rows of the 3×3 transform, unit vectors; the third is their cross product
0x2412f32[3]scaleObservedlocal axis scale; uniform in practice
0x304u32flagsUnknown0x80800000 on all 29,164,671 records
0x341u8instance variation xObservedthe swatch-tint palette row (byte / 255), shared by all pieces of a building
0x351u8instance variation yObservedper-piece value: lights on/off variation, and the tide-mark height of rock shaders
0x361u8instance variation zObservedwindow_linecastroom room centre override
0x371u8instance variation wObservedwindow_linecastroom room width override
0x384u8[4] = 0zeroProven0 on all 29,164,671 records
0x3C1u8material variantObservedrow of the model's variant table; 0 = base
0x3D1u8 = 0zeroProven0 on all 29,164,671 records
0x3E2u16building idObservedindex 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
0x404u32unknownUnknownnot described
0x444u32random valueObservedper-piece
0x484u32 = 0zeroProven0 on all 29,164,671 records
0x4C4u32record tailUnknown0x00000200 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

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

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

Curve record96 bytesFH6100% known

24 floats: two cubic polynomials, P(t) = c0 + c1 t + c2 t² + c3 t³ and Up(t) = c4 + c5 t + c6 t² + c7 t³.

ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x048f32[3][4]curve c0..c3Observedworld metres; c0 is the instance position (the record's fixed point is coarser, within 0.000122 m)
0x3048f32[3][4]up vector c4..c7Observedits 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

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

Which gameplay tags does the game switch on in free roam by default?

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.

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.