# Player paint state handoff audit, r267

The packaged 240SX and 350Z city captures in review entries 345–346 still
show the neutral player-paint fallback. The current viewer has not regressed
to the earlier full-gloss default: `DEFAULT_PLAYER_PAINT.finish` is `default`,
which maps to mode 2 and its 0.38 clearcoat strength. Both cars use the
existing `#858c94` preview color rather than their selected retail paint.

The 240SX GLB has nine untextured, unresolved paint materials and one textured
badge-detail material; its three images are badge and small mechanical atlases.
The 350Z has eight untextured, unresolved paint materials and one textured
detail material, also with three embedded images. Rebinding the detail maps
cannot restore body paint. Earlier Chrome checks confirmed resident sky and
local reflection textures. The evidence does not justify a new tint or
reflection coefficient.

## Static handoff and missing runtime evidence

At `0x638d82`, the constructor reads the optional descriptor from parent
configuration `+0x68c`. Helper `0x61f700` uses descriptor selector `+0x0a`
to resolve key `0x6ba02c05` through manager `0x8a1cc8`. Resolver `0x48f6a0`
returns the named state, or DEFAULT at global `0x7f399c` if missing. The
constructor stores the result in render owner `+0x384` at `0x638d9e`.

For example, draw code `0x63065e–0x630670` reads owner `+0x384` and passes
the pointer with material hash `0xd6d6080a` to `0x48d860`. Other callers use
the same key with pointers from their own call frame. That helper reads the
geometry record at `*(ECX+0x0c)`, its signed material-row count at `+0x1a`,
and the eight-byte key/value rows at `+0x3c`. Matching rows receive the new
state pointer.

The missing edges are the primary player's live configuration/selector, the
replacement-state object used by its active draw caller, and the mapping of
that object's fields into shader constants and texture/blend operations.
The class-2 ambient descriptor key `0x253570af` does not establish the
player's selection. Named records identify candidate resources; their raw
floats are not decoded base colors, roughness or Fresnel parameters.

## Material submission path follow-up

The generic car-part renderer at `0x617400` iterates its 63 part records. In
the ordinary material branch it replaces matching rows for the body-paint,
`WINDSHIELD`, and `0xa7366ae6` hashes through `0x48d860`, then submits the
selected row to `0x5c5930`. This establishes that the windshield row is part
of the same draw submission as the other car material states; it does not
identify the selected state's shader meaning.

`0x5c5930` resolves material-layer descriptors and queues their draw state
through `0x5ceb30`. The later stage audit corrects the original pointer
attribution: queue `+0x1c` feeds lighting helper `0x5bd140`, while queue
`+0x20` feeds material helper `0x5bd4a0`. The latter submits SetMaterial and
D3DRS_TEXTUREFACTOR in its fixed-function branch, or named effect uniforms
in its programmable branch.

The embedded IDI_CAR_FX resource has now been decoded. In both fixed
techniques, texture-factor alpha modulates base-texture alpha with MODULATE2X;
the environment technique also consumes factor RGB. WINDSHIELD's `0.3/1.0`
fields yield a factor-alpha input near 166/255 while material Diffuse alpha
is forced to 1. Opaque base-texture alpha therefore saturates to full output
alpha. A separate texture descriptor selects framebuffer blending, and the
car path prefers its programmable technique if runtime gates and validation
permit it. The live selection and bindings remain uncaptured. See
[the full material/stage trace](car-material-stages-r267.md) for exact offsets,
operations, decoded effect data and fallback conditions. These facts do not
justify setting WebGL glass opacity to 0.3 or choosing a new reflection value.

## Two evidence-tool corrections

The old GDB script tested `[ESP+8]` for the material hash at entry to
`0x48d860`. Disassembly proves a two-argument function returning with `RET 8`:
the key is `[ESP+4]`, and the replacement pointer is `[ESP+8]`. Its internal
`PUSH ESI` accounts for the shifted offsets later in the function. The old
condition would normally miss body draws. The corrected script logs the
caller, incoming state and full 0xa0-byte record, then reads matching rows
from the actual `geometry+0x3c` table. It records old/incoming pointers
without modifying the game.

The exporter previously retained only `+0x30..+0x8f` as 24 raw floats.
Loader `0x48e070` explicitly reads/writes both `+0x80` and `+0x90`
(`0x48e088`, `0x48e0a0`). Thus the latter field and three following floats
were absent from JSON. The export now adds `raw_state_tail_hex`, its source
offset, and `raw_resource_payload_hex` containing all 0xa0 bytes. Every
existing field remains byte-identical. All 98 named mapped resources and
DEFAULT were re-exported from the installed GLOBALB bundle.

These changes preserve evidence and repair capture instrumentation. No
renderer, paint value, lighting, texture binding, geometry or visual cache
version changed; there is no visual before/after claim for this audit.

## Next useful XP capture

A fresh read-only monitor screenshot still shows Windows Product Activation
before login. `/tmp/nfsu2-paint-state.log` does not exist, and no debugger was
attached during this audit.

Once the licensed XP session reaches gameplay, start the corrected
`tools/capture_nfsu2_paint_state.gdb` before the next vehicle construction or
paint change. It uses four hardware breakpoints; run separately from the
traffic/steering capture. Identify the primary player through the wrapper at
`0x8900b4` (actor pointer at wrapper `+0x04`) and correlate its configuration
and render owner with the logged constructor/draw callers. Capture the same
240SX before/after one deliberate color change, then one finish change,
keeping lighting fixed. Record:

- Descriptor, selector, selected map row, state handle and named record.
- Render owner `+0x384` and the actual pointer at each CARSKIN draw.
- Matching material-row address, old/incoming state and complete record.

Then trace the selected state through the material consumer into active shader
constants, sampler bindings and blend state. That last mapping is required
before the raw values can replace the WebGL paint equation. A screenshot or a
record named REGPAINTRED alone does not establish that mapping.

## Static state-to-material decode, r291

The recovered `0x5bd4a0` consumer gives the exported state floats concrete
device/effect roles, but no field is a direct paint RGBA or finish enum. Let
`D0 = s30*(s34,s38,s3c)` and `D1 = s40*(s44,s48,s4c)`. The fixed branch
submits their midpoint as D3DMATERIAL diffuse RGB; the programmable branch
uploads `D0` and `D1-D0` as `DiffuseMin` and `DiffuseRange`. That branch's
pixel color also depends on the 40 live `HarmonicCoeff` floats from the
separate lighting packet at `+0x120`. Specular, environment, power and
texture-factor fields likewise feed those respective device/effect inputs;
they do not identify a standalone body pigment.

Applying those verified equations to the exported payloads gives:

| State resource | Fixed diffuse midpoint | Programmable diffuse min / range | Fixed specular power | Env midpoint |
| --- | --- | --- | ---: | --- |
| `DEFAULT` | `(1.000, 1.000, 1.000)` | `(1,1,1)` / `(0,0,0)` | `10.252` | `(0.0875, 0.0875, 0.0875)` |
| `REGPAINTGRAY` | `(0.878, 0.878, 0.923)` | `(0.800,0.800,0.835)` / `(0.156,0.156,0.176)` | `4.804` | `(0.0872, 0.0871, 0.1097)` |
| `REGPAINTRED` | `(0.945, 0.936, 0.935)` | `(0.878,0.872,0.754)` / `(0.133,0.128,0.362)` | `8.492` | `(0.2552, 0.2540, 0.2520)` |

These are material/effect inputs, not observed final pixel colors. In
particular, the nearly neutral `REGPAINTRED` diffuse midpoint proves that its
resource name cannot be used as an RGB assignment. `DEFAULT` is proven to be
the global lookup fallback when the optional state is absent or unresolved;
it is not evidence that the 240SX draw actually uses that fallback. The
player's optional descriptor/selector at parent `+0x68c`, resolved state at
render owner `+0x384`, and live replacement pointer at the body draw are not
captured.

The fixed equation applies only when type-5 pipeline byte `+0x18` selects the
fixed branch. The programmable technique instead requires the live harmonic
lighting packet and its final named-state uniforms. Technique selection
depends on the runtime shader gate, device checks, and `ValidateTechnique`
result; the selected pipeline index/flag at `+0x0c/+0x18` is also not
captured. Thus the static records provide no reliable per-player color or
finish that can be substituted into the WebGL exporter. For glass, the same
boundary remains: `WINDSHIELD`'s `s50=0.3`, `s54=1` are fixed-function
texture-factor inputs, while actual alpha/blending requires the live base
texture and descriptor `+0x55/+0x56`.

No renderer or exporter values changed from these records. The XP capture
listed above remains necessary to identify the selected player state and
correlate it with the active technique, lighting packet, texture bindings,
and blend state.

## Packaged 240SX appearance capture review, r291

The packaged chase-view capture at `/tmp/nfsu2-current-240sx.png` shows the
body as a nearly uniform pale peach/silver surface, with little visible panel
separation or clearcoat detail. The rear and side glass read as solid dark
blue-gray; the cabin is not visible through the rear window. This is consistent
with the current viewer material path: unresolved body batches use the neutral
preview paint and approximate environment lighting, while the glass GLB
materials have factor alpha `1`, no texture, and no `BLEND` mode. The capture
does not expose a sampler, normal, or technique fault that can be fixed from
the static assets.

No source-backed color or opacity correction follows from this image. A
reliable body match still requires the player's descriptor selector and
resolved state pointer (`owner+0x384`), the live material replacement row,
the selected technique/index/flag, and (for programmable `car`) the lighting
packet's 40 `HarmonicCoeff` floats at `+0x120` with final effect uniforms. A
reliable glass match additionally needs the bound base texture and its alpha,
descriptor bytes `+0x55/+0x56`, and the five BlendState words at `0x86ead8`.
Until those inputs are captured, changing the neutral paint hue, environment
weight, or glass opacity would be a visual guess. No exporter or viewer change
was made from this capture.
