Decals and road markings
FH6 has three kinds of decal: road decal meshes (paint, crossings, kerb grills, seams) that are geometry lying a few
millimetres above the road, mesh decals on facades drawn by the deferred-decal pass, and projected decals, unit boxes that
paint a texture onto whatever surface lies inside them. Decal meshes are identified by shader family, not by name: many are
called Material__27, stripes_3 or Top_2, and the mesh bucket does not help (terrain decals carry bucket 0x38, not the decal
bit 0x02). Observed
Counts come from a recorded census of the Tokyo track Brio; the install is no longer available. Observed
Road decal meshes
Section titled “Road decal meshes”The shader families uber_roaddecal and uber_roaddecalretro draw hard cut-outs into the G-buffer (_AM: DecalMaskATexture
dithered and alpha-tested; the diffuse alpha is not opacity). Road markings are alpha-test decals, parent material
Decal_D1n1p1_Retro_Alphatest, with the paint shape in the alpha. Observed The permutations without AM test no mask and cover every
pixel of the mesh; the mask test, its noise and the far fade are on Other world families. Observed The shipped geometry already lifts them: measured on 1,042
road-decal meshes all sit 5 to 25 mm above the road, terrain or pavement, with patch stacks stepping about 1 mm per layer (7.8, 8.8,
9.9, 10.7 mm) and paint lines about 17.6 mm; 209 facade decal meshes sit a median 2.2 mm in front of the wall. Vertex positions are
quantised to 0.98 mm, so these offsets are deliberate. Observed Another description of the same data gives the decals a
constant 12 mm lift; no measurement behind it is recorded. Inferred
On top of that every non-instanced G-buffer vertex shader adds a per-draw depth offset:
SV_Position.z = clip.z + clip.w · ((rc & 127)/127 · cb2[141].w + cb9[24].w), where rc is a per-draw root constant (possibly the
mesh bucketOrder; road decals use 128 to 255) and the other two are run-time constants, so its size in metres cannot be
recovered. Observed A 24-bit depth buffer with a 0.1 m near clip loses a 7.8 mm gap at about 114 m, which is why a viewer
without the engine’s depth offset shows flicker. Inferred
In the Shibuya pilot area (1,200 m square) the decal-family meshes number 10,316 against 67,860 others. Observed
No shipped file holds the depth, raster or blend state: the .pso files are plain shader containers, and the executable has preset
names such as DecalGreaterEqual and ZBiasDecalOpaque. Unknown The encrypted settings do not hold them either: none of the
install’s 29,250 encrypted zip entries, decrypted, contains DecalGreaterEqual, ZBiasDecalOpaque, GreaterEqual or BlendState
(compared without case), and DecalManager.xml holds only Enabled (Settings files).
Proven
Mesh decals
Section titled “Mesh decals”environmentdeferreddecal mesh permutations (_Standard_DR, _DR_NAO, _DR_ST; 472 ordinary decal materials in a Shibuya area) use
a separate deferred-decal pass that blends into a decal buffer; their outputs are on Other world families.
Observed
Projected decals
Section titled “Projected decals”The environmentdeferreddecal family has mesh permutations and projected ones (_Projected_DR, _Projected_DR_NAO), chosen by
the material switch Projected (type-4 parameter, merged through the parent chain). Projected materials sit on 20-vertex dec_def_*
meshes (mural_a, spceprt_logo, rush_a; 20 ribbon instances): a unit box (position scale 0.5, ±0.5) drawn as a volume. From
environmentdeferreddecalDeferredDecalsLightScenario_I.pcdxil.pso: Observed
inst = PackedTrackInstance (StructuredBuffer, stride 64) = the .pgeo record layout: int3 pos (sign-magnitude 16.16; the shader divides the fraction by 65535), float3 A = row0, float3 B = row1, float3 scaleC = cross(A, B)P = world position from the depth buffer: ndc = (2u − 1, 1 − 2v, depth), P = invViewProj · ndc / w (+ the camera origin offsets in cb2 registers 23 and 24)d = P − inst.posL = (dot(d, A)/scale.x, dot(d, B)/scale.y, dot(d, C)/scale.z) box spacediscard if any |L| > 0.5 outside the unit boxn = normalize(cross(ddx(P), ddy(P))) receiving surface normaldiscard if dot(n, normalize(B/scale.y)) < 0.6 faces within 53.13° of local +Yuv = (1 − saturate(L.x + 0.5), saturate(L.z + 0.5))c = DiffAlphaTex(uv · DiffAlphaUVTiling) (material r9.y, tiling r1.zw)r = RoughMetAOTex(uv · RMAOUVTiling).r (material r9.w, tiling r2.xy)alpha = c.a · DiffRoughIntensity · (VertexAlphaOnOff ? COLOR.a : 1); discard alpha < 0.01out SV_Target0 = (c.rgb, alpha) SV_Target1 = (0, 0, 0, 0) SV_Target2 = (1 − r, 0, 0.5, alpha)So the decal paints DiffAlphaTex over the box’s local XZ square, projected along its local Y axis, onto whatever surface is already
drawn inside the box. The texture indices are in material register 9 (y DiffAlphaTex, z NormalTex, w RoughMetAOTex: TXMP
slot n at dword 20 + index); tilings and switches follow CBMP (r1.zw DiffAlphaUVTiling, r2.xy RMAOUVTiling, r4.w
DiffRoughIntensity, r7.y VertexAlphaOnOff). The same registers feed the _Standard_DR mesh permutation, whose roughness was
matched by interpreting its compiled shader on real materials (max error 1e-7). Observed The world is left-handed and screen y
points down, so cross(ddx P, ddy P) points out of the visible surface toward the camera. Observed On the four dec_def_rush_a
instances (game x ≈ 993, z ≈ −5868) the logos land on the dock plates and read the right way round in a top-down view. Observed
Not in any shipped file: how alpha composites into the decal buffer (the blend state, which the encrypted settings do not hold either), the decal’s
roughness and _NAO normal outputs, and the in-game look. Unknown
The measurement over 1,042 road-decal meshes finds lifts from 5 to 25 mm in steps of about 1 mm, not one constant; a 12 mm constant lift is stated elsewhere without a method. Whether 12 mm is the lift of one class of decal (patches, paint) or a misreading is not established.
The decal buffer’s blend operation and its resolve into the G-buffer are in no readable file. The shader outputs are traced; the composite is not.