# `AIActionDriveTrafficLane` runtime path

This note follows the installed retail executable's class registration into a
class-specific runtime method. It describes observed control flow; several
engine callbacks and route-state fields still need names.

## Class identity and callback association

At read-only-data address `0x00785820`, the `Drive Traffic Lane` class record
contains the action constructor `0x00424290`, a class-specific method pointer
`0x004242d0` at record offset `+8`, and the display-name accessor
`0x004242c0` at `+0x14`. The runtime constructor `0x00424290` calls the common
action constructor with kind `7` and stores `0x00785820` at the new object's
first word. `0x004242c0` returns the string at `0x00785844` (`Drive Traffic
Lane`). The reflection/property constructor at `0x0040ec20` separately
registers the type name `AIActionDriveTrafficLane`.

This links `0x004242d0` to the named action through its runtime class record,
rather than by address proximity. The slot's exact interface label is not
known, so below it is called the action callback.

## Action callback (`0x004242d0`)

The callback reads the actor from the first field of its runtime context and
the actor's route state from actor `+0x2c`. It uses a signed route-phase value
at context `+0x30c` to derive a route mode:

| phase value | selected mode |
|---|---:|
| negative | 0 |
| 0 or 1 | 1 |
| 2 through 9 | 2 |
| 10 or greater | 0 |

If the route state has an object at `+0x6fc`, the callback queries that
object's virtual slot `+0x20` for its current mode. It passes the route cursor
to helper `0x00420ed0` if the modes differ, and to `0x00420d40` if they match
(or if the mode object is absent). The mode table and branch are direct
executable behavior, but the semantic names for modes 0/1/2 are not known.

The two branches differ in a useful way. `0x00420d40` computes an offset or
limit from the route cursor and actor state, consults several world-state
flags, and passes values into controller helpers `0x0041d160` and
`0x0041d530`. `0x00420ed0` handles the other mode: for cursor values 2 through
5 it delegates directly to `0x00420d40`; otherwise it asks `0x0040f4a0` for a
route candidate and tries that route through `0x00420d40`. On success it calls
`0x0040fbe0`; on failure it checks a fallback at context `+0x31c` via
`0x0040fc40`, may call `0x005b5330` on a linked object, and then retries
`0x00420d40`. These calls establish a candidate-route/fallback flow, although
the route-candidate type and controller values are still unnamed.

The fallback guard has a useful exact boundary. `0x0040fc40` reads the
controller's current cursor: `-1` is rejected, cursors `0`, `1`, and `2` are
accepted directly, and other values are delegated to `0x0040aa90`. That
predicate only handles cursors `0`, `1`, and `2..5`; values outside those
ranges fail. For `3..5`, it compares a route-record float at `+0x54` (or the
compiled sentinel when the route reference is absent) with the context limit
at `+0xac+4*cursor`, and accepts when the route value is less than or equal to
that limit. The `0..2` cases are already accepted by the outer guard even if
the metric predicate fails.

The caller-specific selector inputs are now resolved. `0x005ec0b0` returns
the dword at actor `+0x2c`, then `+0x0c`; `0x00420ed0` passes that value as
the selector's route/candidate object, passes the actor and the address of
actor position `+0x60`, and uses `(actor[+0x2c][+0x10] == 0)` as the selector
flag. Both selector bounds are `0xff`, the helper's default-range sentinel.
The flag chooses whether the helper scans route availability bytes `2..5` or
`6..9`; either way it returns a logical selector in `2..5`. The helper ranks
available entries by the absolute difference between each waypoint corridor
metric and its target metric, choosing the smallest difference.

When the selector returns an index, `0x00420ed0` writes it into the first
dword of a temporary four-dword cursor through `0x00405410`, initializes the
other cursor fields, and calls `0x00420d40` with that cursor. If the update
succeeds, `0x0040fbe0` copies the selected four-dword route sample into
controller state and returns success. The persistent copy is conditional on
the candidate update succeeding. If it fails or no candidate was found, the
method checks context cursor `+0x31c` through the fallback guard. A rejected
fallback causes it to invoke `0x005b5330` on the actor's linked object and
reset the controller cursor to zero before retrying the update with the
existing context. That retry's success value is returned without the
candidate-path copy step.

### What this branch does not select

The selector at `0x00420ed0` chooses a cursor phase for the current route
state, not a next route-object edge. Its `0x0040f4a0` scan tests route-header
availability bytes and ranks the available logical selectors `2..5` by the
smallest absolute difference between a corridor metric and actor position.
The flag comes from the current route-state object's byte `+0x10`; zero scans
the direct selector bank `2..5`, nonzero scans the alternate header bank
`6..9`, which still returns a logical selector `2..5`. A selected cursor is
only committed after `0x00420d40` accepts its route update. Failure follows
the separate cursor `+0x31c` guard/fallback path described above.

`0x00420d40` does not consume `0x34122` transition rows as a next-route list.
It prunes and fills the live FuturePoint queue through `0x0041d160` and
`0x0041d530`. The latter scans the controller's runtime candidate-pointer
array, scores candidate points, checks adjacent phase pairs and configured
intervals, then resolves and appends accepted points. The candidate objects,
their route/waypoint pair order, phase weights, route-state byte `+0x10`, and
the committed per-car cursor are not present in the static route/node export.
Consequently neither the `0x00420ed0` cursor trial nor the `0x005d8760`
endpoint-compatible static walker identifies which cross-route branch a
particular ambient car takes. A visualization of `transition_match_record_index`
is useful as a graph diagnostic, but should not be used as an unconditional
car-motion edge.

### Lookahead refresh and distance (`0x00420d40`)

The main branch compares the controller float at `+8` with `999.0` using a
`0.001` tolerance and produces a queue-population parameter of `0` for a match
or `1` otherwise. Under a set of global world/debug flags and a non-null actor
field at `+0x1c`, it is forced to `5`. When helper `0x0041bfe0` returns true,
the controller discards behind-car targets through `0x0041d160`, repopulates
from route candidates via `0x0041d530`, and sets route-state byte `+0x1c9`.

The refresh predicate selects a comparison limit before deciding whether to
rebuild. A nested object type value of `3` selects `10.0`; otherwise global
flag `0x89e7d9` or word `0x890104` selects `4.5`; flag `0x89e7d8` or word
`0x890108` selects `3.0`; the default limit is `1.5`. It evaluates two
phase/candidate-derived float comparisons against that limit. In control-flow
terms, it requests a rebuild directly when the first compared error is above
the limit and the second is below it. In the other cases, it requests a
rebuild when the controller float at `+8` is within `0.001` of `999.0`, the
route target queue count is zero, or controller byte `+0x774` is set. Otherwise
it returns false. The compared values are assembled through `0x00419c60`,
`0x00419cc0`, and `0x0060bfa0`; their semantic names remain unknown.

The branch logic can be reproduced with two intentionally unnamed comparison
inputs:

```text
if comparison_a > limit and comparison_b < limit:
    rebuild = true
else:
    rebuild = near(controller.float_at_8, 999.0, 0.001)
           or target_queue.count == 0
           or controller.byte_at_0x774 != 0
```

The alternate update path derives its lookahead distance from actor field
`+0x430`: normally `max(0.5 × value, 30)`. The comparison in the executable
selects `30` when the half-value is less than or equal to `30`, and keeps the
half-value when it is greater; this is a lower bound, not an upper cap. A
global condition substitutes `0.25 × value` without that clamp. This distance
is passed to `0x0041d160`, which prunes points behind the car and advances the
active queue target, before `0x0041d530` attempts to extend the lookahead path.
The constants `0.5`, `0.25`, `30`, `5`, `999`, and `0.001` are directly
present in the retail code. The semantic identity of actor `+0x430` and the
special global modes remain unresolved, so this should be implemented as a
tunable route-lookahead policy rather than labeled as a proven speed field.

### Forward target queue (`0x0041d160`)

The helper at `0x0041d160`, called from the route-controller path, operates on
a controller object whose signed byte at `+0` is a point count and whose
16-byte point entries begin at `+0x10`. It obtains the actor's current XY
position and forward vector through `0x00401890` and `0x004018a0`. For each
queued point it computes the dot product
`(targetXY - actorXY) · forwardXY`. A positive value marks a point ahead of the
car; points before the first such entry are removed through `0x0040f7a0`.
The helper then shortens the queue to a distance horizon using `0x00415590`
and recomputes path length with `0x00405270`. If there is no usable point
ahead, or the controller is inactive, it clears the queue count and resets
associated state/timestamps.

The helper at `0x00404ef0`, called by `0x00415590`, caches a queue-relative
anchor candidate at `+0x1b4`, along with the timer tick at `+0x1b8`. If called
again during the same timer tick, it reuses the cached index. Otherwise it
scans points in queue order, normalizes the XY vector at actor `+0x70/+0x74`
and each point's XY offset from actor position `+0x60/+0x64`, then tests their
dot product. This establishes `+0x70/+0x74` as a planar motion vector in this
path: its dot product with actor-to-point offsets is used to test whether a
point is ahead along the motion vector. A point is selected when this dot product is positive
and its planar distance squared exceeds the runtime cutoff at `0x827a20`; if
no point qualifies, the initial candidate remains the final queue index. The cutoff is
written by `0x77a6f0` as the square of the float at `0x81cb84`. The initializer
is present in the executable's startup callback table at `0x7e8024`; in the
installed image the source float is zero, so the initialized cutoff is zero.
No later direct write to either address was found in the disassembly, although
an indirect configuration write has not been ruled out. The helper returns
index zero for an empty queue. This is a forward-direction and distance gate,
not a nearest-point search.
`0x00415590` starts from the returned anchor and walks the queued XYZ entries,
accumulating segment distance until it reaches the requested lookahead
distance. It returns the selected point index. The caller adds one, clamps to
the current count, and writes that value back to the queue count; this trims
points beyond the chosen lookahead horizon. It then calls
`0x00405270`, which recomputes the 3D cumulative path length at `+0x1c4` over
the retained entries. Thus this stage keeps a bounded near-term path window;
it does not itself produce a steering angle. A later driver routine
(`0x00416120`) consumes this queue and selects a target position from it.

This is direct evidence for a forward-only lookahead queue: old targets behind
the vehicle are discarded as the car progresses. This helper only prunes the
queue; the driver target sampling stage below turns it into control inputs.

### Future-point queue population (`0x0041d530` → `0x00415d50` → `0x00404cd0`)

The companion population path establishes a useful runtime layout. The object
at controller `+0x31c` combines a future-point queue with a candidate list:

| Offset | Observed representation | Meaning supported by use |
|---|---|---|
| `+0x00` | signed byte | future-point count, capped at 20 |
| `+0x04` | dword | candidate-pointer count |
| `+0x08` | dword pointer array | candidate route/FuturePoint objects |
| `+0x10` | 20 entries × 16 bytes | future-point entries; XYZ floats at entry `+0/+4/+8` |
| `+0x150` | 20 signed 16-bit values | per-point route reference A |
| `+0x178` | 20 signed 16-bit values | per-point route reference B |
| `+0x1a0` | 20 bytes | per-point mode/flag value |
| `+0x1b8/+0x1bc` | dwords | transient values reset when a point is appended |
| `+0x1c4` | float | accumulated future-path length |

`0x0041d530` returns immediately when the candidate count is zero or the
supplied queue count is already 20. Otherwise it chooses a starting candidate
from the current route phase and candidate state, scans later candidate
indexes, evaluates each through `0x0040fc80`, and offers the result to
`0x00415d50`. That helper asks the
candidate object (`+0x5c/+0x5e` route references and `0x004122c0` point
resolver) for a 3D position, then calls `0x00404cd0`. The append helper checks
the 20-point cap.
When the queue is already nonempty, its ordinary append branch rejects a new
point if its planar XY distance from the last queued point is less than 1.0
world unit; equality at 1.0 passes this test. The free-roam candidate wrapper
`0x00415d50` passes this auxiliary append flag as literal `1`, so its candidate
appends always run the continuity branch. In that branch, points within 20
units also pass only if the dot product of the incoming segment and candidate
segment is nonnegative. With two or
more queued points, the incoming segment is `last - previous`; with one queued
point it is `last - actorPosition`, and a null actor pointer rejects that
case. Points at least 20 units from the last point bypass this continuity
check. With the flag clear, only the 1-unit duplicate-distance test applies.

The assembly at `0x0041d530` also exposes the empty-queue scan cursor. It
indexes the current `0x30`-byte phase record using the signed phase byte at
context `+0x308`. When the future-point count is zero, it reads the candidate
index stored at phase-record `+0x28`, advances it by one, and clamps it to the
last valid candidate slot (`candidate_count - 1`). When the queue is already
nonempty, it instead asks `0x004198c0` to select a start index using the
current queue/candidate state. From either start, the population loop advances
through candidate indexes in increasing order and stops at the end of the
candidate array; this loop does not wrap to index zero.

The populated-queue branch makes two calls to `0x004198c0`: the first uses
flag bit 0, start index 0, and threshold `0.5`; the second clears the flags,
uses threshold `0.0`, and starts at `max(firstResult - 1, 0)`. A negative first
result exits this path before candidate access. For a positive first result,
the code calls `0x00412500` on forward-adjacent phase pairs, incrementing the
phase index and decrementing the result after each passing pair. The negative
branch checks preceding pairs in reverse, moving the result toward zero; either
walk stops when a pair fails. The helper checks route availability and, on the
applicable phase-pair path, calls `0x0040b110` to reject configured intervals.
See the [intersection interval note](traffic-intersections.md#phase-pair-interval-table-0x0040b110).
This is a concrete link between route candidate selection and the phase-pair
compatibility rules; it does not label the compared scalar as time or distance.
Evidence: calls at `0x0041d5ac/0x0041d5c9`, phase-pair loops at
`0x0041d805..0x0041d8b6`, and helper `0x00412500`.

The candidate path also samples a speed-keyed two-point curve before calling
`0x004053b0` to update context state at `+0x6e0`. Startup callback `0x0077a730`
sets the descriptor at `0x0081e370` to the two samples at `0x007e9c68`;
`0x00576770` stores the sample pointer and count. The points are
`(0.0, 0.75)` and `(100.0, 0.25)`. Lookup helper `0x00576790` clamps inputs
outside the endpoint range to the endpoint output and linearly interpolates
between adjacent samples. Before lookup, this call path multiplies actor
`+0x430` by `0x0078426c` (`2.237`). Together with another traffic branch's
`10 * 0.44702727` threshold, this is strong evidence that `+0x430` is stored
in m/s and converted to mph for a 0–100 speed curve: `2.237 * 0.44702727` is
`1.000000003` at the stored retail precision. This remains an inference from
the paired conversion constants; the executable does not attach a unit label
to the field.

For this call, `0x004053b0` stores the selected candidate index at context
`+0x6e0`, clears `+0x6e4`, and stores the curve output at `+0x6e8`. If that
output is within `0.001` of `999.0`, it stamps `+0x6ec` with the global timer;
otherwise it clears `+0x6ec`. Since this curve's output is bounded to
`[0.25, 0.75]`, that timer-sentinel case cannot occur here for finite inputs.
The curve and handoff are reproducible, while the output's meaning and units
remain unidentified. A separate speed-weight curve for the driver target
blend is traced below.

The queue builder has five retail call sites, so its behavior is shared by
several route actions rather than belonging only to the lane callback:

| Call site | Caller/context | Observed trigger or setup |
|---|---|---|
| `0x00420a16` | `AIActionGetUnstuck` callback `0x00420940` | Seeds a route point into the queue, then calls the builder with fixed scalar arguments `1.0`, `2`, `0`, and `0.15`. The class record at `0x00785784` supplies this callback and the display name `Get Unstuck`. |
| `0x00420de1` | lane lookahead helper `0x00420d40` | Runs after the queue refresh predicate passes and the old forward queue is pruned; scalar and mode inputs come from the active route/controller records. |
| `0x00420eae` | lane lookahead helper `0x00420d40` | Alternate queue-update branch, after computing a route/controller value from actor state and pruning the queue. |
| `0x00425796` | intersection action callback `0x00425150` | Runs on the callback's route-candidate update path; it first refreshes the temporary queue and passes two sampled floats, a route-state value, and the AI context. |
| `0x0042bdd1` | [`AIActionDriveAtOffset`](traffic-drive-at-offset.md) callback `0x0042bc50` | Runs after the callback copies the actor's route queue to its temporary queue, prunes it, and checks phase/candidate metrics; builder inputs include context floats `+0x310/+0x314`. The class record at `0x00785a18` supplies this callback and the display name `Drive at offset`. |

The two calls from `0x00420d40` belong to the lane lookahead update, and the
`0x00425796` call is inside the registered intersection callback. The other
two call sites belong to `AIActionGetUnstuck` and `AIActionDriveAtOffset`, as
shown by their class records. Callers pass action-specific scalar state, but
all converge on the same ordered scan and append checks. This shows the
future-point builder is shared by ordinary lane following, intersection
handling, and recovery/offset actions. The raw disassembly for these call
sites is at
`0x00420a16`, `0x00420de1`, `0x00420eae`, `0x00425796`, and `0x0042bdd1`.

The `AIActionGetUnstuck` callback at `0x00420940` first clears its temporary
queue at action `+0x20` through `0x0041d160` and resets action bytes/floats
`+0x1fc`, `+0x1f8`, `+0x1fe`, and `+0x1fd`. If `0x005ec0b0` returns no
route object, it stores the global float at `0x007836dc` in action `+0x1f0`
and exits. Otherwise it gets the current FuturePoint through `0x004032a0`,
resolves a point with `0x004122c0`, derives a route ID from the returned
object's dword `+0x0c` masked by `0x7ff`, and appends that point with mode `2`
through `0x00404cd0`. It then extends the queue through `0x0041d530` with
`(1.0, 2, 0, 0.15)` as the four trailing scalar/mode arguments. Finally,
the actor route object's dword `+0x24` selects one of two global floats for
action `+0x1f0` (`0x007836d8` when it equals `3`, otherwise `0x007836dc`).
The callback's name supports calling this the unstuck action, but the route
ID, mode byte, and two final floats still have no stronger semantic labels.

For each scanned candidate, `0x0041d530` may call helper `0x00415b70` before
computing the candidate's bounded value through `0x0040fc80` and offering it
to `0x00415d50`. A global mode/descriptor condition can set a local flag that
bypasses `0x00415b70`; the loop still reaches the bounded-value and append
helpers. The phase helper returns a selector and updates a float in place. Its
input is initialized from a phase-record float plus the candidate metric from
the initial selection step. If it finds a neighboring phase with a nonzero
route byte, it adds the `0x004123c0` candidate delta described below. If the
current phase route byte is already nonzero, it leaves the phase and metric
unchanged.

The bounded-value helper `0x0040fc80` calls the candidate's virtual slot
`+0x18`, scales that result by a caller-supplied float, and combines it with
phase-indexed candidate floats. For selector 0 or 1, the two values are at
candidate offsets `+0x18` and `+0x2c`; selectors 2 through 5 read the arrays
starting at `+0x14` and `+0x28`; other selectors use the global zero value.
It bounds the intermediate scalar against caller-supplied limits. Global
mode flags and candidate descriptor `+0x1c` enable an additional adjustment.
The caller's x87 path then compares its scratch values using the `0.001`
constant at `0x0078435c`; the scratch operands' gameplay meanings remain
unassigned. This comparison does not reject the candidate or end the scan:
the loop proceeds to `0x00415d50` for every scanned candidate until it reaches
the candidate-array end or queue capacity.

The bounded scalar is passed as argument 2 to `0x00415d50`, which forwards it
to the candidate position resolver `0x004122c0` along with the selected phase
and the candidate's stored `+0x54` value. It therefore helps construct the
point that will be appended; it is not a boolean filter or a winner score.
Candidate selection occurs when `0x004198c0` chooses the starting index. The
subsequent scan is ordered and append-oriented, with `0x00404cd0` independently
enforcing the 20-point cap, minimum duplicate spacing, and continuity test.
The bounded scalar's units and its exact contribution to the resolved point
remain unknown.

`0x00415b70` reads candidate route references at `+0x5c/+0x5e`, asks the
candidate's virtual slot `+0x04` for a selector mode, and checks bytes in the
resolved route object at `+0x82+selector`. For phase indexes 2 through 5, it
checks the current phase first, then nearby phases in the order `+1, -1, +2,
-2, +3, -3`, skipping indexes outside 2 through 5. If a nearby phase passes
the route-byte check, it calls `0x004123c0` for a candidate-derived float and
returns that phase index with the float. For the `0x00784a48` FuturePoint subtype,
virtual slot `+0x04` is `0x0050d510` and returns zero, so the phase index is
used directly. For subtype `0x00784aa0`, slot `+0x04` is `0x0040cb20` and
returns one: phase index 0 maps to byte selector 1, indexes 2..5 map to
`index+4`, and other indexes keep their value. The raw route-byte meanings
and returned float's units remain unknown. This supports describing
`0x00415b70` as a
phase/route-availability and metric helper, not as an unexplained generic
filter.

The subtype mapping is compactly represented as:

| FuturePoint vtable | `+0x04` result | Route-byte selector for phase `p` |
|---|---:|---|
| `0x00784a48` | `0` | `p` |
| `0x00784aa0` | `1` | `1` for `p=0`; `p+4` for `2<=p<6`; otherwise `p` |

The route-byte portion of the helper is therefore reproducible as:

```text
selector(p, mode):
  if mode == 1 and p == 0: return 1
  if mode == 1 and 2 <= p < 6: return p + 4
  return p

if 2 <= phase < 6:
  if route[0x82 + selector(phase, mode)] != 0:
    return the current phase through the helper's direct result path
  for p in [phase+1, phase-1, phase+2, phase-2, phase+3, phase-3]:
    if 2 <= p < 6 and route[0x82 + selector(p, mode)] != 0:
      metric = input_metric + 0x004123c0(candidate, current_point, p)
      return p, metric
```

The helper's arithmetic is now resolved for valid route references. It calls
candidate virtual slot `+0x34` with the resolved candidate waypoint and the
queue tail (`queue.entries[queue.count-1]`). Both FuturePoint subtypes
calculate the signed cross-track component of `currentPoint.xy - waypoint.xy`
against the candidate waypoint's route basis:

```text
cross_track = (currentPoint.x - waypoint.x) * routeBasis.y
            - (currentPoint.y - waypoint.y) * routeBasis.x
```

Subtype `0x00784a48` reads the packed basis from the candidate waypoint; its
virtual `+0x34` target is `0x0040d1b0`. Subtype `0x00784aa0` obtains the
waypoint basis through `0x00401aa0`; its virtual `+0x34` target is
`0x0040d410`. The helper then subtracts a selector-specific candidate float
and the waypoint corridor term returned by `0x00401c40`:

```text
candidate_value = candidate[+0x04]        if selector == 0
                  candidate[+4*selector]  if 2 <= selector <= 5
                  0                       otherwise
corridor_selector = candidate.virtual_0x08(selector)
corridor_value = 0x00401c40(waypoint, corridor_selector)
candidate_delta = candidate.virtual_0x34(waypoint, currentPoint)
                  - candidate_value - corridor_value
```

If either route reference at candidate `+0x5c/+0x5e` is `0xffff`, the helper
returns the compiled float sentinel `0x7d7d3c` instead. The caller
`0x00415b70` adds `candidate_delta` to its incoming float and returns that
sum with the selected phase. This identifies the geometry, arithmetic, and
subtraction order; the incoming float's units and the candidate float's
authored meaning remain unknown. The pseudocode above describes this
contribution after a neighboring phase passes the route-byte test.

This cursor and local phase scan are separate from the two-way `0x34122`
transition walker: they inspect runtime candidate objects and route flags,
while the walker only returns an endpoint-compatible record.

The empty-queue path has a separate gate. It requires a non-null actor pointer
and candidate distance of at least 1 unit. It accepts if the candidate
direction has positive dot product with a normalized actor direction, or if
the actor-relative distance exceeds a threshold: 20 units normally, or 8 when
queue byte `+1` is set. The direction source switches at actor speed `+0x430`
of 1.0: it uses actor `+0x70/+0x74` at or above that value, and `+0x90/+0x94`
below it. The helper clears queue byte `+1` after this first-point decision.
These actor direction fields' gameplay meanings remain unknown.

On success the helper writes XYZ plus the two route references and one mode
byte, adds the 3D Euclidean distance from the previous point (helper
`0x0043ce10`) to `+0x1c4`, and increments the count. The route references are
preserved exactly; which one selects which side/branch is not named yet.

The candidate position resolver `0x004122c0` has a distinct invalid-reference
fallback: if either signed 16-bit candidate reference at `+0x5c/+0x5e` is
`0xffff`, it writes `(0,0,0)` to the output. Otherwise it resolves the route
object through the table at `0x0088a334` and computes the waypoint address as
`route+0x8c+waypointIndex*0x38`. The resolver then combines caller-supplied
scalars with candidate fields `+0x54/+0x58`, selects an additional candidate
value by selector (selector `0` reads `+0x04`, selectors `2..5` read
`+4*selector`, and other selectors use zero), calls the waypoint helper
`0x00401c40`, and dispatches the waypoint plus computed values through
candidate virtual slot `+0x38`. This is a subtype-specific final step, not one
shared point-construction implementation. Candidate virtual slot `+0x08` is
also called with the selector. The fields and selector alter the result, but
their gameplay labels remain unresolved; do not assume the adjustment is a
simple lane-width offset.

The resolver's four stack arguments are output pointer, float `a`, float `b`,
and selector `s`. Before calling virtual slot `+0x38`, it prepares two weights
(names below are descriptive):

```text
weight_basis = a + candidate[+0x58] - candidate[+0x54]
weight_offset = b

if s != -1:
  candidate_value = candidate[+0x04]       if s == 0
                    candidate[+4*s]         if 2 <= s <= 5
                    0                       otherwise
  offset_selector = candidate.virtual_0x08(s)
  corridor = 0x00401c40(resolved_waypoint, offset_selector)
  weight_offset += candidate_value + corridor

candidate.virtual_0x38(resolved_waypoint, output, weight_basis, weight_offset)
```

For the `0x00784aa0` subtype, the vtable wrapper passes `weight_offset` to the
first point-plus-vector call as its scale, then passes `weight_basis` to the
in-place second call. The first displacement uses the route vector rotated in
XY; the second uses the route vector directly. The waypoint fields and stack
locals recover the transform as:

```text
F = (-previous[+0x18]/32767, -previous[+0x1a]/32767)
base = (waypoint.x, waypoint.y, waypoint.height_score_raw_i16/8)
lateral = (F.y, -F.x, -waypoint[+0x20]/128)
route_vector = (F.x, F.y, -previous[+0x21]/128)
output = base + weight_offset*lateral + weight_basis*route_vector
```

For the first route waypoint, `previous` means that waypoint itself. This
identifies the geometric roles of the weights in the transform. The caller's
choice of weight values and the traffic-side meaning of each sign remain
separate questions.

Virtual slot `+0x08`, which supplies the byte selector to `0x00401c40`, is
also subtype-specific. Vtable `0x00784a48` uses `0x0040d080`, which returns the
selector unchanged. Vtable `0x00784aa0` uses `0x0040d3a0`, which maps selector
`0` to `1`, selectors `2..5` to selector plus `4`, and leaves other values
unchanged. The helper then reads the signed byte at waypoint `+0x22` plus this
mapped selector and scales it by `/128`.

The waypoint helper `0x00401c40` reads the two unsigned width fields at
waypoint `+0x14/+0x16`. It converts each to route-world units (the fields are
scaled by `1/256`), selects the larger extent, and adds a signed-byte adjustment
read using the helper's other argument and waypoint address (`+0x22` in the
address expression). The adjustment is scaled by `1/128`. Thus the selected
scalar is the larger route extent plus a selector-specific signed adjustment;
the transform uses it as the lateral displacement weight. Which traffic side
corresponds to a positive value remains unresolved.

### FuturePoint runtime variants

The FuturePoint serializer is registered at `0x0040ac20`; its reset/initialize
routine is `0x0040ad20`. The reset routine initializes the route and waypoint
references at `+0x5c/+0x5e` to `-1`, clears the dword at `+0x60` and byte at
`+0x74`, and initializes several data banks, including `+0x04`, `+0x08..+0x14`,
`+0x18..+0x28`, `+0x2c..+0x3c`, and `+0x40..+0x50`. During FuturePoints setup
(`0x004118e1..0x00411951`), allocated entries first receive the base vtable
`0x007846f0`, are reset, and then receive one of two derived vtables:
`0x00784a48` or `0x00784aa0`. The list setup serializes entries through
`0x0040ac20` at `0x004119b7`.

The list is part of a tagged object stream, not a row embedded in the static
`0x34122` traffic graph table. The list serializer at `0x00411790` opens the
`FuturePoints` stream tag (`0x00785068`), reads its count and pointer array,
constructs each FuturePoint from the per-entry type word, then calls
`0x0040ac20` for each instance. The FuturePoint serializer reads `+0x5c/+0x5e`
as a two-byte pair through `0x00571710`, reads `+0x54` and
`+0x58` as separate four-byte values through `0x00571770`, and also serializes
the other object fields and float banks. The same list serializer is called
from two larger stream serializers: `0x00413910` passes its member at `+0x31c`,
and `0x00418580` passes its member at `+0x68`. The second caller is nested in
the `RealController` stream tag (`0x00785298`); the first uses stream tag
`0x007850b8`. This establishes that the two point-transform weights are
persisted on runtime FuturePoint instances; the current extracted `0x34122`
records do not contain them. A read-only scan of
the XP guest's NTFS MFT located `GLOBAL/InGameFreeRoam.bun`; that 379,752-byte
bundle is front-end data, and its visible resource strings are HUD/FEng names.
The executable also has runtime FuturePoint producers, so the serialized form
alone is not evidence that these weights originate in a shipped asset.

The append helper at `0x00418a50` creates a FuturePoint through
`0x00411b50`, then stores caller-provided values at `+0x54/+0x58`, a dword at
`+0x60`, and route/waypoint references at `+0x5c/+0x5e`. It also copies a
four-float bank into `+0x08..+0x14`. One caller, `0x00426870`, resolves a
waypoint using `0x005d89b0` before appending it. Another, `0x0041f2d0`, extends
an existing list and advances both weight arguments in increments of the
float32 constant `0x00783c88 = 3.0`.

The caller at `0x00426810` is a specifically zero-seeded case. It takes the
route index from the low 11 bits of its descriptor dword at `+0x0c`, resolves
the source waypoint index with `0x005d89b0`, and passes both references to
`0x00418a50`. The two four-float local banks supplied to that append are
zero-initialized at `0x00426839..0x00426858`; its scalar arguments for
FuturePoint `+0x54`, `+0x58`, and `+0x60` are also zero. The resulting point
therefore resolves to the waypoint base with no lateral displacement. This
proves that this producer seeds a route-center point. It does not prove that
all controller candidates use zero offsets: the later selector reads its own
phase-indexed candidate values and corridor adjustments.

The caller first asks `0x00418c50` whether interpolation is warranted. With
an existing list, this gate requires the incoming and last FuturePoint to
reference different route objects, a valid incoming route/waypoint pair, the
incoming waypoint's `+0x1c` word to equal `0xffff`, and all bits in mask
`0x01f80000` to be set in its flags at `+0x0c`. It resolves the incoming
point, projects it along the last FuturePoint's route basis with
`0x0040ae30`, then compares the resulting scalar displacement with a
threshold: 10.0 when byte `0x0089e7d8` or dword `0x00890108` is nonzero,
otherwise 5.0. The gate allows interpolation when this displacement is
greater than the selected threshold. An empty list passes the gate
immediately.

When allowed, the sampler at `0x00418da0` inserts intermediate FuturePoints
between the pair of route references. It resolves both endpoints through
`0x004122c0`, but its subdivision count is based on a scalar span derived from
their `+0x54` fields: that span is divided by
`0x00783c68 = 8.0` and converted to an integer count. The generated entries
advance their `+0x54/+0x58` values using this scalar step and resolve their
positions from the route data. The 40-entry cap can truncate insertion. This
is runtime subdivision in the FuturePoint scalar domain; the disassembly does
not establish that the subdivisions are eight world units apart. The precise
gameplay interpretation of the two scalar fields, and which callers feed
each mode, still need to be resolved.

The scalar helper `0x0040ae30`, used by the sampler, starts from the global
float at `0x007d7d3c` (zero in the installed image). For a valid route and
waypoint reference it calls the FuturePoint's virtual slot `+0x2c` with the
resolved waypoint and a caller-supplied XY point, then adds
`candidate[+0x54] - candidate[+0x58]`. Both derived implementations at that
slot return a 2D dot product: the caller point minus a route reference point,
projected onto the packed route basis scaled by the runtime's basis factor.
The `0x00784a48` subtype uses the resolved waypoint and its packed basis; the
`0x00784aa0` subtype obtains the basis through its route context and uses the
FuturePoint's XY as the projection origin. With an invalid route/waypoint
reference, the helper returns the global starting value. So for valid points
the helper combines a route-axis projection with the stored scalar difference;
the returned scalar's gameplay label remains unidentified.

Setup selects between the derived vtables by testing a per-entry dword:
zero selects `0x00784a48`, and nonzero selects `0x00784aa0`. Their virtual
slot `+0x04` implementations are `0x0050d510` (returns `0`) and `0x0040cb20`
(returns `1`), respectively. This is the boolean consumed by `0x00415b70` to
choose direct versus remapped phase-byte selectors; it is not a runtime
property inferred from the candidate's route data.

The derived vtables differ at virtual slot `+0x38`. For vtable `0x00784a48`,
that slot targets wrapper `0x0040d0f0`, which forwards to `0x005dd360`. For
vtable `0x00784aa0`, the slot targets `0x0040d380`, which forwards to
`0x005dd490`. The forwarding wrapper passes its first stack argument as the
callee's `this`; that value is the selected `0x34121` waypoint. The next stack
argument is the output-vector pointer, followed by two scalar weights. This
corrects the misleading shorthand that the transform reads the FuturePoint
object as its `this`.

The scalar accessor at `0x0040d110` is instead in slot `+0x40` of the
`0x00784a48` vtable. It reads waypoint word `+0x10`, converts it by `/256`,
and returns the float; it is not the slot `+0x38` point constructor.

`0x005dd490` reads waypoint XY floats `+0/+4`, signed height word `+0x12`,
signed bytes `+0x20/+0x21`, and the two signed words `+0x18/+0x1a`. The height
word is converted by `<<13` then
multiplied by `0x784278` (`1/65536`), giving `/8`. Signed byte offsets are
converted by `<<9` and the same scale, giving `/128`; `0x005dd490` negates the
current `+0x20` value, while `0x0040d310` reads the previous waypoint's `+0x21`
value (or current at index zero) and negates it. The helper `0x00401aa0`
supplies a two-component basis from the signed words `+0x18/+0x1a` of the
previous waypoint, falling back to current at index zero. Both components are
negated and scaled by `0x81e7fc`.

The setup routine at `0x0077a620` sets `0x81e7fc` by dividing `1.0` by the
`32767.0` constant at `0x00783490` and stores the result as float32. The route
basis components are each the signed word multiplied by that stored scale,
negated, then rounded to float32. This is a signed normalization, not a
guessed world-unit scale. The route exporter materializes
the source waypoint index, both raw words, and this XY pair in each waypoint
record. It also exposes the signed `+0x20/+0x21` bytes and their scaled 3D
vector components, plus all ten signed corridor adjustment bytes addressed
from `+0x22`. For each of those ten selector offsets, the export includes the
evaluated corridor extent: `max(width_a,width_b)/256 + signed_adjustment/128`.

The authored-data check supports this basis decoding: over 14,695 exported
waypoints its XY norm ranges from `0.999957` to `1.000000`. For each of 13,541
nonzero consecutive route segments, the transform's previous-waypoint basis
is anti-parallel to the segment's increasing-index XY delta (mean dot-product
cosine `-1.000` to floating-point precision). This establishes a unit route
direction basis and its orientation relative to file order; it does not by
itself say which direction a traffic vehicle traverses the route.

The WebGL traffic preview uses this decoded basis as its forward direction. An
audit of the 1,154 exported route records found a negative dot product between
the basis and every route's increasing-index segment direction (route 450 is
`-0.991`; many long routes are within rounding error of `-1`). The preview
therefore reverses the sampled waypoint sequence and keeps a source-index map
so the executable's `0x34122` junction links still resolve to the original
waypoints. This is supported by the driver's forward-only lookahead and its
use of the waypoint basis, though the traced code does not yet prove the full
retail traffic spawn-direction selection for every route class.

Both `0x00401aa0` and `0x0040d310` first call `0x005d89f0` to resolve the
waypoint immediately preceding the input waypoint on its route. When such a
waypoint exists, they read its corresponding `+0x18/+0x1a` words or `+0x21`
byte; for the first waypoint, they fall back to the input waypoint's own
fields. `0x005d89f0` derives the waypoint index from its pointer within the
route's `+0x8c` array and returns the prior `0x38`-byte entry, or null at index
zero.

Finally, `0x0043d0c0` is exactly `out = origin + scale * vector` for three
float components. `0x005dd490` uses it to add the lateral displacement to the
waypoint base, then adds the route-vector displacement in place. The signs
and vector geometry are recovered; the dynamic values' gameplay calibration
and which lateral side a positive value represents remain open. This point
construction dispatch is through virtual slot `+0x38`; the `0x0040d110`
scalar accessor is a separate virtual slot `+0x40` method.

### Candidate point scoring (`0x004198c0`)

The selector scans candidate FuturePoint objects and resolves each candidate's
route/waypoint reference through `0x004122c0`. It measures planar distance
from the resolved point to the input target XY. It then reads the signed
16-bit value at route waypoint `+0x12`, converts it to world units by `/8`,
and compares that value with the input candidate Z. The candidate score adds
`10 × abs(route-height-value - candidate-Z)` to the planar distance, and the
lowest score wins. This establishes a height-sensitive route candidate choice;
the field is called `height_score` in the exports because its use in the
scorer is proven. A separate XY comparison with the exported L4RA `RDP_`
triangles shows that its `/8` value also tracks sampled road-surface elevation
closely: 9,790 of 9,812 waypoints that fall inside such a triangle are within
0.25 world units. The mesh comparison and its unmatched/layered-surface caveats
are recorded in the [route source section of the index](README.md#34121-route-objects-and-ordered-road-points).

The assembly adds two details to that score description. The planar distance
is capped at `99,999.0` (`0x784360`) before adding the height penalty, so the
effective expression is `min(distanceXY, 99999) + 10 * abs(height - targetZ)`.
The initial scan starts at the fourth stack argument, raised to zero if it is
negative, and continues while its cursor is below the candidate count. Its
next-index update also uses the current planar distance, multiplied by `-0.02`
and passed through `0x75bed4`; it is not always a simple `index++`. The first
candidate initializes the best result; later candidates replace it only when
their score is strictly lower, preserving the earlier index on ties.

The return value can then be adjusted around that best-scoring index. Starting
at the winner, the helper resolves the candidate waypoint and calls its
virtual slot `+0x2c` when both route references are valid. Let `m` be that
returned float, `candidate[+0x58]` the candidate field, and `t` the fifth stack
argument. Flag bit 0 advances the index when `candidate[+0x58] - m < t`; flag
bit 1 decrements it when `candidate[+0x58] - m >= t`. If neither condition
moves the index, the walk stops. An attempted move outside `[0, candidateCount)`
returns `-1`. With both flag bits clear, the function returns the original
best-scoring index. This is an index-neighbor threshold walk over the candidate
list, not another distance minimization; the metric and threshold units are
not identified.

Evidence: `0x004198c0` in `/tmp/nfsu2-disassembly.txt` around lines 35735–35920;
float constants `0x784360` and `0x783cc4` decode from the installed `SPEED2.EXE`
as `99,999.0` and `10.0` respectively.

The route exporter preserves that signed raw field and its `/8` value for
every waypoint. The `0x34122` node export carries the same value for resolved
nodes. The executable's `10.0` penalty multiplier and `99,999.0` planar cap
are directly observed; selector flags and threshold arguments control the
post-score walk described above.

At the resolver call inside this scan, the scorer supplies `a=candidate[+0x54]`
and `b=0`, so `weight_basis` reduces exactly to `candidate[+0x58]`; its
selector `s` is forwarded from the scorer's selector argument. The helper
`0x00419b20` forwards its fourth argument as that selector. The helper
`0x00415d50` uses the same `a` value and therefore the same reduced basis, but
forwards its own `b` and selector arguments. These callers establish where the
weight inputs come from; selector meaning depends on the caller, and the
caller-provided offset remains unnamed.

All direct scorer call sites were enumerated. The selector is the scorer's
third stack argument; the table records the pushed operand at each call, not
the runtime value of register operands:

| Call site | Selector operand | Evidence/use |
|---|---|---|
| `0x00419b5e` | `EBP` | forwarded from `0x00419b20` argument 4 |
| `0x0041a045` | `EDI` | wrapper `0x0041a030` argument 1 |
| `0x0041ceb5`, `0x0041ced2` | `0` | two default selector lookups |
| `0x0041d5ac`, `0x0041d5c9` | `EDI` | two lookups in the same caller |
| `0x0041d7e4` | `EBX` | normalized phase returned by route helper `0x0040f4a0` |
| `0x0041e8b2` | `ESI` | caller-derived selector |
| `0x0041fd87` | `0` | default selector lookup |
| `0x004280aa`, `0x0042820c` | `EAX` | caller-derived selectors |
| `0x0042a218`, `0x0042a46c` | `EBX` | selector shared across paired lookups |
| `0x0042cc7d` | `EBX` | caller-derived selector |
| `0x005f1f43` | `EAX` | result of candidate virtual slot `+0x1c` |

This inventory proves that the resolver's selector is not globally fixed: the
game has both literal-zero queries and paths that pass state-derived values.
Most register operands still need their caller-specific state decoded before
they can be assigned phase, side, or lane meanings. The scorer's selector
values also should not be conflated with the separate route-byte selector
returned by candidate virtual slot `+0x08`.

One path is now resolved: caller `0x0041d720` passes its actor's current
FuturePoint to `0x0040f4a0`, then passes the returned index in `EBX` as scorer
selector `s` at `0x0041d7e4`. The helper checks route availability bytes at
route `+0x82+selector` and selects the available byte whose waypoint corridor
metric is closest to its computed target metric. Depending on the actor flag
at `+0x10`, it searches either byte selectors `2..5` or `6..9`. The latter
range is normalized by subtracting four before return, so both paths return
logical phase selectors `2..5`. In the scorer's `0x00784aa0` candidate subtype,
virtual slot `+0x08` maps that logical phase to the corresponding `6..9`
waypoint adjustment byte; the `0x00784a48` subtype uses the phase number
directly. This connects the helper's bank choice to the selector transform,
while leaving the actor flag's gameplay label unresolved.

The selector helper's recovered scan can be represented as:

```text
bank_base = 6 if actor[+0x10] == 0 else 2
best_selector = -1
best_error = large_sentinel
for byte_selector in bank_base .. bank_base+3:
  if route[+0x82+byte_selector] != 0:
    error = abs(waypoint_corridor_metric(byte_selector) - target_metric)
    if error < best_error:
      best_error = error
      best_selector = byte_selector
return best_selector - 4 if bank_base == 6 else best_selector
```

The helper returns `-1` if no available route byte passes through the scan.
The target and corridor metrics are assembled from the actor's current
FuturePoint and route waypoint fields; the comparison and selector-bank
behavior are proven, while the flag's gameplay meaning is not.

The target metric in this scan is now resolved from the exact v1.2 executable.
In the ordinary branch (`[esp+0x28] == 0` at function entry), it reads signed
words `+0x18/+0x1a` from the current waypoint and scales both by the runtime
float at `0x81e7fc` (`1/32767`). In the alternate branch it obtains the same
signed, negated previous-waypoint basis used by `0x00401aa0`. Both branches
then compute the same 2D determinant-like scalar against the passed target
point and current FuturePoint:

```text
dx = target.x - future_point.x
dy = target.y - future_point.y
target_metric = dx * basis_y - dy * basis_x
```

The helper compares this metric with `0x00401c40(waypoint, selector)`, whose
value is `max(width_a, width_b)/256 + signed_adjustment[selector]/128`, and
chooses the available selector with the smallest absolute difference. This
shows the selector is a nearest authored corridor-edge metric in the route
basis. It does not supply an actor's runtime FuturePoint scalar or justify
placing traffic at a guessed fixed lane offset; the route points alone still
do not recreate those live values.

The route exporter now exposes the nonzero selector bytes as
`nonzero_route_byte_selectors` in `freeroam-traffic-routes.json`, derived from
each retained 0x8c-byte route header. Across its 1,154 route objects, all eight
bytes for selectors `2..9` are binary `0/1` values. Counts of route objects
with a nonzero byte at each selector are: `2:202`, `3:371`, `4:556`, `5:129`,
`6:133`, `7:263`, `8:372`, and `9:129`. Of these route objects, 507 have no
enabled selector in either bank, so this helper returns `-1` for them when
that route is scanned. This dataset check confirms the binary's byte test
against authored city data and makes the selector availability directly
readable to a recreation.

`0x0040fc80` is a scalar-producing helper, not a boolean acceptance test. It
calls candidate virtual slot `+0x18`, scales that result by one argument, and
clamps it to a mode-selected interval. For mode `0`, the interval endpoints
come from candidate `+0x18` and `+0x2c`; for modes `2..5`, they come from
`+0x14+4*mode` and `+0x28+4*mode`. Other modes select zero endpoints. A
caller-supplied offset is added to the first endpoint and subtracted from the
second. In its ordinary branch, negative endpoints are floored at zero before
the scaled virtual result is clamped. Under a particular set of global-state
conditions, if an auxiliary argument's object at `+0x1c` is non-null and the
endpoints are ordered, both endpoints are instead collapsed to their midpoint.
The caller uses this float in subsequent candidate/queue calculations. These
are mechanically recovered interval operations; their units and gameplay
labels are not established.

This confirms the route follower builds a bounded, ordered 3D lookahead path
from runtime route candidates, and that it preserves route provenance beside
each point. Other candidate-order and fallback thresholds and the extra checks
at `0x00404cd0` still need decoding. It also remains unproven which of these
candidates are specifically ambient-traffic paths versus shared AI navigation
data.

The callback then clears action byte `+0x1fc`, queries a route-state virtual
method at slot `+0x7c`, and stores its float result at action `+0x1f0`. Under
specific global-state conditions, if the actor's descriptor pointer at `+0x14`
has dword `+0x04 == 2`, it calls
`0x0040e390`. That helper first clears action byte `+0x1ff`, then requires
actor field `+0x14`'s second dword to equal 2, a zero result from helper
`0x59fa30(actor+0x40)`, and a true result from route-state virtual slot
`+0xa0(context)` before setting `+0x1ff`. This exposes `+0x1ff` as a
conditional route/action flag, though the route method's predicate is still
unknown. The callback finally sets action flags `+0x1fd/+0x1fe`. These fields
look like state for a subsequent AI update, but their exact names are
unresolved.

### Shared action-output handoff (`0x0041c4d2` to `0x0041c5d6`)

The route action's output fields are consumed by a shared AI update routine.
Before consuming an action, the routine asks helper `0x00411dd0` for
controller values using the current route phase, a separate selector value,
and actor state. The selector is passed to the candidate lookup at
`0x0040ab10` and also selects the candidate float: selector values 0 or 1 use
`+0x40`, while values 2 through 5 use `+0x3c + 4*selector`. Unsupported
selector values and an empty candidate list produce the executable's zero
sentinel. These candidate floats contribute to
the bounds used below. Their gameplay meaning is not yet named, but this ties
the shared action limit to route-candidate data rather than only to a global
constant. When a later candidate resolves to the same indexed waypoint, the
helper blends the two mode-specific floats using interval values stored at
candidate `+0x54/+0x58`; if it finds no matching later candidate, it returns
the selected candidate's mode-specific float directly. The arithmetic is a
linear interpolation, though the coordinate represented by that interval is
not yet identified.

After calculating a bounded scalar from context/controller state, it reads the
current action's `+0x1f0`, `+0x1f4`, `+0x1f8`, and `+0x1fd` fields. If
`+0x1fc` is set, it substitutes zero for the `+0x1f4/+0x1f8` pair; otherwise
it forwards the action's stored values. It limits the scalar against the
action's `+0x1f0` and a further value returned by a virtual call at slot
`+0x4c`, then calls virtual slot `+0x38` on the object stored at route-state
`+0x08`, passing the resulting scalar, the two values, and byte `+0x1fd`.

This proves the action outputs are combined and handed to a route-state-owned
control object each update. The receiving class and storage behavior are now
identified below, although the gameplay meaning of the tuple components
remains unresolved.

The outer object in this path is identified as `AICarController`: the handoff
reaches it through the actor's pointer at `actor+0x2c`, and its constructor at
`0x00420500` stores the actor at controller `+0x04` and installs vtable
`0x007852b0`. Its reflected serializer at `0x00428260` registers the name
`AICarController`. The output call then dereferences controller `+0x08` and
dispatches that child object's vtable slot `+0x38`. The derived constructor
at `0x004260b0` attaches the child there when the actor's type field `+0x24`
equals 3. Under one set of global conditions it allocates `0x460` bytes and
calls `0x00425ab0`; otherwise it allocates `0x250` bytes and calls
`0x00425e40`. These constructors install vtables `0x00784eb0` and
`0x00784f40`, respectively.

The first vtable's reflected serializer at `0x00415dd0` registers the name
`APsychoDriver`. Its slot `+0x38` is `0x004054a0`, which stores the four
handoff arguments directly: scalar at child `+0x230`, the two dwords at
`+0x234/+0x238`, and the byte at `+0x23c`. The second vtable's slot `+0x38`
is `0x00405b60`; it stores the first argument at child `+0x22c`, replacing it
with `31.2919` when it exceeds that compiled limit. It ignores the other three
arguments. The shared AI tuple is therefore handled by the
attached driver implementation, while the gameplay meaning of its components
remains unresolved.

### Desired-speed scalar provenance

The scalar in the shared handoff is not taken directly from the route key or
from a car-definition speed field. The action callback `0x004242d0` writes
action `+0x1f0` from route/controller virtual slot `+0x7c`. In the derived
`AICarController` used for actor type `3`, constructor `0x004260b0` installs
vtable `0x00785370`; that vtable's `+0x7c` entry is `0x005a9f50`, which returns
the float at `0x00784550`. The installed value there is `1000.0`. This is a
large action ceiling in this concrete controller class, not the operating
traffic speed. Other dynamic controller classes may supply a different value.

The shared output routine `0x0041c380` (tail thunk `0x004204e0`) forms the
first handoff float and calls the driver's virtual slot `+0x38` at
`0x0041c5d3`. Its bounds include two values returned by `0x00411dd0` for the
current route phase and a nearby phase/offset, the action ceiling at
`action+0x1f0`, a further controller virtual-slot `+0x4c` value, and a speed
shaping term based on actor `+0x430`. The latter uses the executable's
`1.3` and `8.0` constants; another speed-derived bound uses `1.3` and `20.0`.
If context byte `+0x77c` is set, the candidate scalar is replaced by
`0x007d7d3c`, which is `0.0` in the installed image. Action byte `+0x1fc`
zeros the other two tuple components, not the scalar.

`0x00411dd0` reads the selected runtime route-candidate record from its
candidate list. It returns candidate `+0x40` for selectors `0/1`, or
`+0x3c+4*selector` for selectors `2..5`; when a neighboring candidate matches
the route sample, helper `0x00411fa0` interpolates values using the candidate
interval fields `+0x54/+0x58`. The resulting candidate data, not a formula
over `routeKey`, supplies the route-dependent part of the scalar. The action's
`+0x1f0` and the virtual `+0x4c` value are ceilings on this result. The exact
comparison/interpolation bounds are mechanically traced in
`0x0041c380..0x0041c5d6` and `0x00411dd0`; their gameplay units remain
unidentified.

The receiver is `APsychoDriver` for the established type-3 `AICarController`
path: vtable slot `+0x38` resolves to `0x004054a0`, which stores the first
argument at child `+0x230`. Its update at `0x00405790` forms
`abs(child+0x230) - actor+0x430` for the controller at child `+0x270`.
This confirms a speed-related target error. It does not establish a direct
km/h or mph interpretation for the scalar, and the later per-frame update
also reuses `+0x230` as heading-error state. The current route exports do not
contain these live candidate-list values or the dynamic `+0x4c` bound, so the
retail value cannot yet be calculated per traffic car from route geometry
alone. A fixed preview speed is still a presentation choice, not an extracted
retail speed.

### AI controller sample filters (`0x00576970`, `0x005769d0`)

The nested controller objects used by the AI driver maintain a bounded sample
window. For this filter, byte `+0x05` is the capacity, byte `+0x06` is the
number of samples currently counted (capped at capacity), byte `+0x07` is the
next ring-buffer index, `+0x08` is a running sum, `+0x0c` is the average, and
`+0x10` points to the float samples. `0x00576970` inserts one value, removes
the overwritten sample from the sum, adds the new value, and recomputes
`sum / sampleCount`; it then advances and wraps the ring index. Before the
window fills, it averages only the samples received so far.

`0x005769d0` seeds every sample slot with one value, sets the running sum to
`capacity * value`, resets the ring index to zero, and sets the counted-sample
byte to zero. The seeded samples remain in the sum even though the denominator
starts at zero and then grows from one on the first insertion; this makes the
startup behavior depend on both the seed and the incoming samples. The
controller setter `0x0057a010` invokes this initializer for
the child filters at `+0x28` and `+0x50`, while `0x00576bb0` feeds values
through the parent and both child filters using `0x00576970`. This gives a
concrete low-level smoothing mechanism for AI control signals, though the
purpose of each nested channel remains unknown.

The update method's argument flow is also explicit. It saves its first float
argument as the current value at controller `+0x7c`, moves the old value there
to `+0x80`, and forms `delta = current - previous`. With its second float
argument `s`, it sends `s` to the parent filter, `delta / s` to child `+0x50`,
and `(previous + C*delta) * s` to child `+0x28`, where `C` is the float at
`0x00784260`. The third and fourth byte arguments independently force the
`delta / s` and child-`+0x28` values to zero. The calls from `0x004058ee` pass
zero for both flags; other callers, including `0x00418449` and `0x0042123f`,
pass a runtime flag for one of them. No nonzero guard for `s` appears in the
division path, so callers are responsible for its valid range. These formulas
describe the controller's actual signals without assigning them steering,
acceleration, or another gameplay name.

The first `APsychoDriver` vtable also exposes the update path. At vtable offsets
`+0x70/+0x74`, entries point to `0x00405780/0x00405790`. The latter reads the
stored scalar at child `+0x230`, takes its absolute value, subtracts the owning
actor's float at `+0x430`, and passes that difference to a controller object at
child `+0x270` through `0x0057a010`. This establishes that the first handoff
component is consumed as a speed-related target: the driver forms
`abs(component) - actor[+0x430]` as the controller input. The executable alone
does not establish the units or whether the target is speed, throttle, or a
normalized speed command, so those labels remain provisional.

The adjacent update callback `0x00405780` clears that same `+0x270` controller
with a zero input. A separate driver update at `0x004056c0` calls
`0x004054d0` to compute three float outputs, then sends them to the controller
objects at child `+0x264`, `+0x268`, and `+0x26c` using `0x00576c40`. The
calculation uses the driver's target XY at `+0x224/+0x228`, actor XY at
`+0x60/+0x64`, actor heading at `+0x40c`, and actor speed at `+0x430`; this
strongly suggests position/heading tracking, but the three channels' exact
roles are not named by the binary evidence. The companion callback
`0x00405720` routes those same three computed outputs through `0x0057a010`
instead. `0x0057a010` writes the supplied value into its controller
subcontrollers at `+0x28/+0x50` and clears their pending state, while
`0x00576c40` writes controller target state at `+0x80` and clears `+0x7c`.
The same vtable has `0x00416120` at offset `+0x94`; its pointer is present in
the read-only vtable data beginning at `0x00784eb0`. This confirms the
queue-to-driver target routine below belongs to `APsychoDriver`, rather than
being inferred from matching object offsets alone.

### Driver target calculation (`0x004054d0`)

The helper called by `0x004056c0` and `0x00405720` makes the route target
handoff more concrete. It reads the stored target XY from child `+0x224/+0x228`
and the actor's current XY from actor `+0x60/+0x64`. Helper `0x0043e6d0`
converts the vector from current position to target into the executable's
16-bit angle representation. The helper subtracts that angle from actor heading
`u16 +0x40c`, wraps the difference into the signed half-turn interval, converts
it to a float, and writes it to the first output pointer. This directly
establishes a heading-error signal from the route target.

The second output is speed-conditioned. Startup callback `0x0077a7b0` sets
the descriptor at `0x0081e7e4` to the two points at `0x007e9d34`; helper
`0x00576770` stores that pointer and count. The points are `(5.0, 0.0)` and
`(10.0, 1.0)`. Lookup helper `0x00576790` clamps at either endpoint and
linearly interpolates between them, so the weight is exactly
`clamp((actor[+0x430] - 5) / 5, 0, 1)`.

The helper converts the actor's motion vector `+0x70/+0x74` directly to an angle,
then computes its wrapped difference from the route-target direction. It
blends this vector-derived heading error with the error from the actor's stored
heading `+0x40c`:

```text
weight = clamp((actor[+0x430] - 5) / 5, 0, 1)
output = stored_heading_error * (1 - weight)
       + direction_vector_heading_error * weight
```

Thus values at or below 5 use only the stored-heading error; values at or
above 10 use only the error from actor vector `+0x70/+0x74`; values between
them crossfade linearly. The queue anchor's test at `0x00404ef0` establishes
that the same actor vector is used as a forward-direction input. Its producer
and magnitude remain unresolved. The `TrafficMover` callback separately
writes a rate-scaled position-displacement vector to the component pointer at
`owner+0x20`, offsets `+0x30/+0x34/+0x38`; the inspected code does not prove
that component storage aliases actor `+0x70/+0x74/+0x78`. See
[`traffic-mover.md`](traffic-mover.md#complete-post-movement-handoff-order)
for that mover-side vector update. The owner transform-sync method also keeps
its position delta at actor `+0x50/+0x54/+0x58` and does not write the
`+0x70/+0x74` pair. Actor `+0x430` is likely m/s based on the
reciprocal conversion constants described above; this unit label is inferred
rather than stored in the binary.

A separate consumer clarifies how the pair is used. Helper `0x004036e0`
constructs a target displacement from target XY minus actor `+0x60/+0x64`,
normalizes that displacement and actor `+0x70/+0x74`, then compares their dot
product with a caller-supplied threshold. If actor speed `+0x430` is below
`1.0`, it substitutes the first two floats at actor `+0x90/+0x94` for the
direction pair before normalizing. This is a forward-cone predicate: the
stored pair acts as the direction of travel at higher speed, while the
transform basis supplies direction at low speed. It does not reveal the
pair's writer, original magnitude, or whether it aliases the mover component's
three-dimensional displacement vector.

The third output derives from another angle comparison: the helper measures a
direction using driver state at child `+0x10` and actor position, compares it
with a global angle reference, and wraps the result to the same signed half-turn
interval before writing it. The exact use of this channel is unknown. The three
outputs are stored into separate controller objects at child
`+0x264/+0x268/+0x26c`; their opaque controller classes prevent safely naming
them as yaw, lateral position, or correction without further tracing.

The `APsychoDriver` constructor at `0x00425ab0` allocates an `0x84`-byte
controller object for each of these three slots. It configures all three via
`0x004016b0` with the same raw arguments `(5, 5, 30.0)`, then initializes
their target state to zero through `0x0057a010`. On update, the three
independently computed signals are stored as targets through `0x00576c40`;
the driver update later feeds each controller through `0x00576bb0`, which
updates that object's parent filter and two child filters. This proves the
channels share a controller implementation and configuration, but does not
identify the meaning or units of the raw configuration arguments or the
channel signals.

`0x004056c0` submits all three values through controller setter `0x00576c40`;
`0x00405720` submits the same tuple through `0x0057a010`. This shows that the
lane-following path computes a target heading error and additional
angle-control channels, while a separate child `+0x270` controller receives
the speed-related target error described above. The mover/physics code consumes
the resulting controls, but the mapping from the three angle channels to
vehicle physics remains unresolved.

### Queue-to-driver target (`0x00416120` → `0x00415630`)

`0x00416120` is the bridge from the forward-point queue to the
`APsychoDriver` target at `+0x224/+0x228`. It reads the actor pointer at child
`+0x220`, actor field `+0x430`, and the queue object at child `+0x10`. With a
non-empty queue it calls `0x00404af0` with the queue and actor position. Let `q`
be the returned float. It computes the first path distance as
`clamp(15 + 0.75 * abs(q) + 0.04 * actor[+0x430], 15, 30)` world units, then
calls `0x00415630` to sample the queue at that distance. The constants and
clamps are direct executable values; `q`'s exact units and sign meaning remain
unknown. It makes further samples at other speed-scaled distances; these feed
the driver's target and nearby control state.

The helper `0x00404af0` returns zero when the queue has no points. For one
point, it returns the 3D Euclidean distance between that point and the supplied
actor position. For multiple points it scans consecutive queue segments in
order. For each segment it computes the dot product of `(actorPosition -
segmentStart)` with the segment direction. It retains the segment-relative
vectors while that dot product is positive, and stops at the first segment
where it is nonpositive; if the first segment already fails, it uses that
first segment. It then combines the selected segment and actor-offset data via
cross-product and normalization helpers to produce `q`. There is no direct
distance comparison across segments, so this is not a global nearest-segment
search. The normalization helper `0x0043cf10` returns a unit vector and uses
`(1,0,0)` when its input has zero length, so a degenerate cross product takes
that explicit fallback. Also, `q` is branch-dependent: with one queued point it
is a nonnegative 3D distance, while with multiple points it comes from the
selected segment/offset calculation. Its exact geometric units and sign
convention in the multi-point case remain unresolved. The lookahead formula
uses `abs(q)`, but `q` should not be treated as one consistent signed lateral
error across both queue sizes.

The path-distance samples use global tuning constants from the executable.
Where their formulas are not yet resolved, a recreation should keep them
configurable instead of assigning gameplay names to them.

`0x00415630` has three cases. With an empty queue it copies the actor's current
XYZ to the output and returns `-1` (`0xffffffff`). With one queued point it
normalizes the actor-to-point vector to the requested scalar, adds that vector
to the actor position, and returns `0`; this is a ray sample and can extend
beyond the sole queued point. With multiple points it obtains an anchor index
through `0x00404ef0`, then accumulates 3D segment lengths through the 16-byte
XYZ entries until the requested path distance falls within a segment. The
interior case interpolates on that segment and returns its preceding point
index. If the requested distance exceeds the queued path, the code has a
separate terminal-segment path: it writes an extrapolated sample and returns
the last point index (`count-1`). Preserve this distinction: `-1` specifically
denotes an empty queue, while valid-path sampling returns a point index.

The selected target XY is written to driver `+0x224/+0x228`. The driver helper
`0x004054d0` then converts that position into the wrapped heading error used by
the angle-control channels above. The confirmed chain is therefore:

```text
route candidates
  → bounded 3D future-point queue
  → speed-scaled path-distance samples
  → driver target XY
  → wrapped heading error
  → angle-control objects
```

Actor `+0x430` participates in both lookahead distances and the
speed-controller subtraction. This supports calling it speed-related; the
paired conversion constants above further suggest m/s as its stored unit.

The same update routine resolves a second path sample, converts the vector
between the two sampled positions into a 16-bit heading, and compares route
direction with actor heading. It then enters state-dependent branches involving
driver fields `+0x22c/+0x230/+0x23c`, world-state flags, and additional angular
thresholds. These branches update target/control state and can change the
speed-related scalar, but their complete semantics are not assigned here: the
binary uses several scaled degree constants and state predicates whose
relationship to braking and route mode needs additional tracing. The sample
and heading comparisons establish turn-aware control inputs; they do not by
themselves prove a particular braking curve.

### Turn-aware target refresh (`0x00416120`)

The branch at `0x00416120` contains that second stage. It first samples the
queue at a distance
`clamp(15 + 0.75*abs(q) + 0.04*actor[+0x430], 15, 30)`, where `q` is returned
by `0x00404af0`. It then samples again at a distance derived from the first
sample plus `0.08*actor[+0x430]`, clamped by compiled bounds `8` and `12`.
Those sampled positions are subtracted and passed to the shared angle
converter `0x0043e6d0`; the resulting 16-bit direction is compared with the
actor's 16-bit heading at `+0x40c`. The constants above were decoded directly
from the executable's read-only data section.

The constants map to these retail values: `0x783a9c = 15`,
`0x783aa8 = 0.75`, `0x783aa0 = 0.04`, `0x783a8c = 15`,
`0x783a90 = 30`, `0x783aa4 = 0.08`, `0x783a94 = 8`, and
`0x783a98 = 12`. Since the first sample distance is at least 15, the second
distance clamps to 12 for nonnegative `actor[+0x430]`; it is therefore a
fixed 12-unit lookahead under that observed speed range. The low-speed branch
compares `actor[+0x430]` against `10 * 0.44702727` (about `4.47027` m/s,
corresponding to 10 mph if the unit inference above is correct). The
executable's angle conversion constant at
`0x784244` is `0.0174532924` radians per degree, so the shaping constants 45,
35, and 70 used nearby are degree values converted to radians, not raw radians.

The routine compares the wrapped route-heading difference with a
state-dependent `90°` or `120°` threshold, checks whether its previous
turn-related field `+0x22c` is negative, and checks byte `+0x23c`. Depending
on those checks it writes the driver's
throttle/brake/e-brake command slots, calls a virtual method, and takes one of
several paths that update the scalar at `+0x230`. The frame delta also enters
the update path. This confirms turn-aware, stateful control around the
lookahead target. The exact state names, response curve, and virtual method
meaning remain unresolved.

The normal branch is more concrete than that summary suggests. It is reached
only when the turn flag is clear and actor `+0x430` exceeds `10 * 0.447027`
(the comparison is strict); it then computes the unsigned actor-to-route
heading error with `0x00591d90`, differentiates that error against the previous
`+0x230` value over `dt`, wraps the difference, and stores the new error back
to `+0x230`. This gives the controller both current alignment error and its
per-frame rate. Its local angle bounds include `45°`, `35°`, and the dynamic
`clamp(45° + 0.03*a, 45°, 70°)` value. Further clamps combine those bounds,
the wrapped error/rate, `+0x22c`, and `dt`; several other stack locals still
need provenance mapping.

The following output section at `0x00416603..0x00416753` exposes a
three-factor e-brake product. For readability, `u`, `v`, and `s` below name
the values read at `[esp+0x28]`, `[esp+0x34]`, and `[esp+0x18]`, respectively:

```text
e_brake = clamp((1.5 - u) * 0.11185, 0, 1)
         * clamp((0.2617994 - v) * 5.729578, 0, 1)
         * clamp((s - 8.940545) * 0.11185, 0, 1)
```

The constants are stored in the executable's read-only data section. The
formula is exact for the observed locals and command slot `+0x210`. The third
local `s` is actor speed at `+0x430`: the function
loads it at `0x0041612d` and saves it in the stack slot consumed by the third
factor. That factor is exactly the linear ramp
`clamp((speed - 8.940545) / 8.940545, 0, 1)` at the stored float precision.
The paired `2.237` conversion elsewhere in this note supports reading
`+0x430` as m/s; under that inferred unit, the ramp starts at 20 mph and
reaches full scale at 40 mph. This speed gate multiplies the two
error-dependent factors, so the e-brake output is zero below the lower speed
threshold and cannot exceed their product between the thresholds. The middle
factor is `clamp((15° - v) / 10°, 0, 1)` when `v` is expressed in radians,
because its constants are `15°` in radians and the reciprocal of `10°` in
radians. It is full scale for `v <= 5°`, drops linearly from 5° to 15°, and
is zero at or above 15°.

The stack provenance narrows both remaining values. The local that becomes
`u` is copied from the alignment-error working value produced by
`0x00591d90` at
`0x00416463..0x00416468`. The routine conditionally negates that value using
the residual in its local `+0x20` before the e-brake product: it keeps the
positive alignment error when the residual is negative, and negates it when
the residual is nonnegative (`0x00416636..0x00416655`). The local that
becomes `v` is copied from local `+0x24`, which is the bearing from actor
position `+0x60/+0x64` to the selected driver target `+0x224/+0x228`: the callback
computes that vector's 16-bit angle at `0x004162a3..0x004162bb`, converts it
to radians, and wraps it into the signed half-turn interval before storing it
at local `+0x24` (`0x004162c5..0x00416320`). Thus `v` is that wrapped
target-bearing angle with a sign selected by alignment error: it keeps the
bearing when the alignment-error local is nonpositive and negates it when
that local is positive (`0x00416659..0x00416678`). For finite values the two
branches reduce to:

```text
u = residual < 0 ? alignment_error : -alignment_error
v = alignment_error > 0 ? -target_bearing : target_bearing
```

`alignment_error` is the `[0, π]` result of `0x00591d90`; `target_bearing`
is the wrapped angle from actor position to the selected lookahead target.
The residual that selects `u`'s sign is also traced: at `0x004165df..0x004165ff`
the callback wraps the difference between the alignment error saved from
`0x00591d90` and the clamped intermediate it stores in driver `+0x238`:

```text
residual = wrap_radians(alignment_error - driver[+0x238])
```

A preceding clamp in the same shaping sequence is now source-mapped. The
callback reads the actor speed saved from `actor+0x430` at function entry,
multiplies it by `-0.1`, and clamps the result to `[0,1]` at
`0x00416535..0x0041654f`:

```text
speed_factor = clamp(-0.1 * actor[+0x430], 0, 1)
```

The speed factor is stored in a local and read again at `0x00416566`, where it
multiplies a wrapped angular difference before a clamp at `0x0041656d`. The
main branch reaches this shaping sequence only when actor speed exceeds
`10 * 0.447027`, so for positive speed this particular factor clamps to zero.
The target bearing remains the input to the `v` factor described above. A
second shaping factor is produced by the clamp at
`0x004165a4`; it weights another wrapped angular difference, which is clamped
at `0x004165c7`. The two clamped terms are added and passed through a final
clamp at `0x004165d6`; that result is stored in driver `+0x238` at
`0x004165ee`. In schematic form:

```text
term_a = clamp(wrap_radians(angle_a) * speed_factor, bounds_a)
term_b = clamp(wrap_radians(angle_b) * shaping_factor, bounds_b)
driver[+0x238] = clamp(term_a + term_b, bounds_c)
```

The angle operands, second shaping factor's inputs, and all three clamp
bounds remain unlabeled. This establishes that the speed-weighted term
influences the value subtracted from alignment error in the residual, though
its weight is zero on the positive-speed main-branch path.
In the non-turn output branch, the code forms a brake
candidate from `(angle_local + 2.235136) * 0.11185`, clamps it to `[0,1]`,
and writes it through `0x005eb8f0`. It then derives a bounded throttle
candidate using the angle local, `20°` (`0.34906584` radians), the factor
`0.9`, and another local; it clears e-brake and calls virtual slot `+0x28`
with `-1`. These are the literal operations at `0x004167b1..0x00416839`,
without assigning a gameplay meaning to the angle and stack values.

If the turn flag is set, a separate path at `0x0041683e..0x00416900` compares
`abs(+0x22c)` with `2.0`. Values below that threshold select full brake and
zero throttle. Otherwise it creates signed brake/throttle outputs from
clamped locals, clears e-brake, and calls slot `+0x28` with `0` for a set turn
flag or `-1` otherwise; it also rate-limits a heading target through
`0x00405990`. This preserves the branch structure and command effects without
claiming the unknown virtual callback semantics.

One heading input to the response is computed by `0x00591d90`. In this call
the receiver is actor `+0x40`; the helper reads the pair at actor `+0x70/+0x74`,
converts it to the executable's 16-bit angle with `0x0043c570`, then compares
that angle with actor heading `+0x40c`. Its returned float is the unsigned
shortest angular separation in radians:

```text
direction16 = atan2_lookup(actor[+0x74], actor[+0x70])
delta16 = (actor.heading16 - direction16) & 0xffff
error = delta16 * (2*pi / 65536)
if error >= pi:
    error = 2*pi - error
```

The result is in `[0, pi]`; it has no left/right sign. On this branch,
`+0x230` is read as the previous sample, used in the wrapped difference, and
then replaced by the new error:

```text
previous_error = driver[+0x230]
alignment_rate = wrap_radians(error - previous_error) / dt
driver[+0x230] = error
```

The rate is saved as a temporary and reused in the later clamp/response
calculation. The division has no local zero-`dt` guard. The first visit's
prior value depends on update ordering and any earlier setter call.

The field roles vary across the driver's update stages. Setter `0x004054a0`
stores the AI action's scalar in `+0x230`, and update helper `0x00405790`
interprets its absolute value as a target to compare with actor speed
`+0x430`. But `0x00416120` uses the incoming value as prior state for the
wrapped heading-error difference, then overwrites `+0x230` with the current
unsigned heading error. Treat `+0x230` as an overloaded field whose value
depends on update order; the available traces do not yet reconcile the setter,
consumer, and per-frame update ordering. In this method, angle differences go
through `0x00405940`, which wraps a radian value into the signed full-turn
interval using compiled `2π` constants. The exact speed-versus-turn response
and its intermediate sign convention still need tracing, so preserve the
observed order and keep its thresholds and rates configurable. The newly
decoded output transforms above narrow that uncertainty to the stack-local
inputs and their semantic roles, rather than the arithmetic constants or
command-slot effects.

There is a second overwrite on the main turn-calculation branch: after the
clamp sequence at `0x00416503`–`0x004165d6`, the routine stores that branch's
selected intermediate at `driver+0x238`. The source value comes from the
calculation built from the sampled heading error, the action scalar at
`+0x22c`, and angle/rate bounds; it is then used in a wrapped residual
calculation immediately afterward. Thus `+0x238` is not preserved as the
second float supplied by setter `0x004054a0` on this branch. The routine does
not read `+0x234` in the inspected body. Neither observation establishes the
tuple's intended API meaning, and the `+0x238` write is branch-specific rather
than unconditional.

One bound in that response can be written exactly. At `0x004164e3`–
`0x00416503`, the code computes a local shaping value
`actor_speed * 0.03 + 45°`, with the degree term converted to radians, then
clamps it between `45°` and `70°`:

```text
shape_limit = clamp(actor[+0x430] * 0.03 + radians(45),
                    radians(45), radians(70))
```

For the offset calculation, take the stack base immediately after the
`push edi` at `0x00416139`. At `0x004164e9`, ESP is eight bytes below that base,
so `[esp+0x24]` resolves to base `+0x18`, the speed local written at
`0x0041613d`. The first sample distance is a separate local at base `+0x1c`,
written at `0x004161a3`; it is not the input to this cap. Under the inferred
m/s unit for actor speed, the cap rises from `45°` and reaches its `70°` upper
bound at about `14.54 m/s` (about `32.5 mph`). Since this calculation is on
the branch gated by speed strictly above `10 * 0.447027`, its effective cap
there starts just above `52.68°`. The same base-relative mapping at
`0x00416535` shows that `speed_factor` also reads the actor-speed local.
The next clamps combine these speed-shaped bounds with wrapped differences
and frame delta before updating driver state.

The nearby helper `0x00405990` advances a separate 16-bit driver heading
field at `+0x1e0` toward a requested angle. It converts the stored unsigned
angle to radians, wraps the target difference, limits the per-call change to
`360° * dt` (equivalent to `2π * dt` radians), adds that step, and converts the
result back to the game's 16-bit turn representation. The constants decoded
from the executable are `360.0`, degrees-to-radians `0.0174532924`, and the
unsigned-angle scale `2π/65536`. This gives a direct maximum heading-change
rate of one full turn per second for this helper's call path. It is a
rate-limited heading update, distinct from the speed target stored at
`+0x230`.

### Turn decision to driver command slots

The control effects at the end of `0x00416120` connect to the shared RealDriver
setters documented in [player-controls.md](player-controls.md): `0x005eb910`
writes throttle slot `+0x20c`, `0x005eb900` writes e-brake slot `+0x210`, and
`0x005eb8f0` writes brake slot `+0x214`. The turn decision sets a local flag
when the wrapped route-to-vehicle heading difference exceeds the threshold
selected from driver state `+0x1e4`: `90°` when zero, `120°` when nonzero. A
negative value at driver `+0x22c` also sets that flag. If the flag is clear, a separate comparison between
`10 * 0.447` and actor `+0x430` selects the same downstream branch for the
low-speed case. This comparison independently supports the inferred m/s unit
for `+0x430`; the unit remains a semantic inference from the conversion value.

When driver byte `+0x23c` is set, the routine takes an early override path: it
writes `1.0` to brake and throttle, calls the rate-limited heading helper, and
calls the driver's virtual slot `+0x28` with `1`. On the other path, the
routine computes throttle and brake candidates from route-angle and state
values, clears e-brake through `0x005eb900(0)`, and calls virtual slot `+0x28`
with `0` when the local turn flag is set or `-1` otherwise. The `+0x230`
desired-speed target is revised in this update as well. This closes the
observed chain from route geometry into the driver's three longitudinal
command slots; the full response curve and the virtual callback's
meaning remain open.

The route-angle gate is strict: it compares the absolute wrapped difference
with `90°` or `120°` as described above, then ORs in the negative-`+0x22c`
test. If neither test sets the local flag, the routine still selects the same
turn or low-speed branch when actor
`+0x430` is below `10 × 0.447`; equality does not take that branch. The
nonzero `+0x23c` override is checked before either route/low-speed branch and
therefore takes priority over both. These comparisons are at `0x0041639d`,
`0x004163b0..0x004163ce`, `0x00416414..0x00416429`, and
`0x004163d4..0x0041640c`.

The turn update also reads and writes a compact state at driver `+0x1e4`
through vtable getter/setter `0x00405b20/0x00405b40` (slots `+0x20/+0x28` on
the `0x00784eb0` vtable used by this method). Before the route-angle test, a
zero state selects a `90°` angle threshold (`120° - 30°`); any nonzero state
selects `120°`. The state is set to `1` by the `+0x23c` override, to `-1` by
the small-`abs(+0x22c)` brake case, and to `0` or `-1` by a later branch
according to the local turn flag. Thus `+0x1e4` carries a real three-valued
history signal that changes the next update's turn-angle gate. Its authored
meaning is unknown, but a recreation should preserve the numeric values and
the zero-versus-nonzero threshold selection.

There is also an explicit reset path. Vtable method `0x00410010` (slot
`+0x1c`) stores its byte argument at driver `+0x254`; when the argument is
zero, it calls slot `+0x30`, whose implementation `0x00410030` writes `-1` to
`+0x1e4`. This establishes a reset transition for the history signal. The
ordinary caller and timing of this flag-clear path have not been traced.

Inside the selected turn/low-speed branch, a second strict comparison checks
`abs(driver[+0x22c]) < 2.0`. When it passes, the routine writes brake `1.0`
through `0x005eb8f0`, throttle `0.0` through `0x005eb910`, and calls driver
vtable slot `+0x28` with `-1`; this path does not write e-brake. The opposite
comparison continues into a separate rate- and angle-shaped output path. This
is an exact command case, but it does not establish what `+0x22c` represents
or how the complementary branch computes its outputs.

One important piece of that curve is now identified: helper `0x00401090`
returns `min(max(value, lower), upper)`. Its first argument is the candidate,
the second is a lower bound, and the third is an upper bound. At
`0x004164e3..0x00416503`, the candidate is `45° * π/180 + 0.03 * speed`,
bounded between `45° * π/180` and `70° * π/180`; the stack slot resolves to
the actor-speed local saved from `actor+0x430` at `0x0041613d`. The separate
first route-sample distance is saved at `0x004161a3` and clamped to `[15,30]`,
but does not feed this cap. Under the inferred m/s speed unit, the cap reaches
its `70°` upper bound at about `14.54 m/s` (about `32.5 mph`).
Several other calls to the same helper bound neighboring candidates before
they are written to driver state. This establishes part of the response as
piecewise bounded angle shaping, while leaving the complete multi-branch
formula and its final relationship to the desired-speed update open.

Readable control-flow sketch:

```text
onDriveTrafficLaneAction(action, context):
    actor = context.actor
    route = actor.route_state
    phase = signed_i32(context + 0x30c)
    desiredMode = phase < 0 or phase >= 10 ? 0 : (phase < 2 ? 1 : 2)

    if route.modeController exists:
        activeMode = route.modeController.virtual_20(route.argument)
    else:
        activeMode = 0

    if desiredMode != activeMode:
        tryAlternateRouteAndFallback(routeCursor=context+0x30c, context, route)
    else:
        updateRouteController(routeCursor=context+0x30c, context, route)

    action.metric = route.virtual_7c()
    if trafficWorldCondition and actor.descriptor[+0x04] == 2:
        updateTrafficActionFlag(action, context)  # may set action byte +0x1ff
    action.flags_1fd_1fe = true
```

`tryAlternateRouteAndFallback` is `0x00420ed0`; `updateRouteController` is
`0x00420d40`.
The callback receives a pointer context, and the pseudocode's `actor`,
`route`, `phase`, and `metric` are explanatory names for the observed fields.

## `RealController` route-update callback (`0x0042caa0`)

The reflected `RealController` class has vtable `0x00785230`: slot `+0x08`
points to its serializer `0x00418580`, and slot `+0x0c` points to the update
callback `0x0042caa0`. Constructor `0x004266d0` installs that vtable and
initializes the extended state described in [player-controls.md](player-controls.md).
This is a concrete route-following controller, not evidence of the player's
raw keyboard/gamepad input path.

The update callback takes a frame delta and uses owner/controller fields at
`+0x04`, `+0x08`, `+0x0c`, `+0x10`, `+0x68`, and `+0x6c`. Its observed order is:

1. If global dword `0x0082a3a4` is positive, return through the short path.
2. Call `0x0040a390` and compare its result with byte `+0x10`. On a state
   transition, clear the nested object at `+0x68`, mask the low two bits from
   sixteen consecutive dwords beginning at `+0x14`, and update byte `+0x10`.
3. Call `0x0041d0e0`, `0x00418620`, `0x0042a190`, and `0x0042a360` in that
   order. The second helper resolves route graph state; the roles of the other
   three helpers remain only partially named.
4. Initialize a temporary lookahead structure and pass it through
   `0x0041d160`, the same queue-pruning helper used by the route-following
   controller path above.
5. If `+0x6c` is nonzero, resolve a candidate from the owner and route index
   `+0x0c`, then query route state through `0x00419bc0`. The callback caches
   the route index at `+0x3e0`, a route sample at `+0x3d8`, and a rate/state
   value at `+0x3dc`. When owner `+0x42c` passes a compiled threshold, it calls
   `0x004221f0` with the resolved sample and route index.
6. If `+0x6c` is zero, branch on owner `+0x42c`: one path invokes
   `0x004259c0`; the other uses owner position/heading fields through
   `0x00404910` and then `0x00420a60`.
7. Query an object at controller `+0x08` through vtable slot `+0x3c`. If the
   owner's 16-bit field at `+0x0a` is `0xffff`, also update global route state
   using `0x00414bf0` and `0x00414d20`.

Readable behavior sketch, with deliberately neutral field names:

```text
updateRealController(controller, dt):
    if globalModeCounter > 0:
        return

    mode = queryRouteMode()
    if mode != controller.modeByte:
        reset(controller.pathState)
        for each of 16 controller words beginning at +0x14:
            word &= ~3
        controller.modeByte = (mode == 0)

    updateRouteState(controller)
    resolveRouteGraphLinks(controller)
    updateRouteCandidateState(controller)
    updateRouteTargetState(controller)

    lookahead = emptyTemporaryPath()
    pruneLookahead(lookahead)
    if controller.routeCount != 0:
        candidate = resolveCandidate(controller.owner, controller.routeIndex)
        sample = queryRoute(controller.pathState, candidate, dt)
        updateRouteSampleCache(controller, sample, dt)
        if ownerField_42c <= threshold:
            submitRouteControl(controller, sample, controller.routeIndex)
    else:
        updateFallbackTargetOrRouteState(controller)
```

This callback links the graph transition and bounded-lookahead work to one
runtime controller. Its owner speed field, route cursor, queue, and control
handoff are observed, but the branch's behavioral meaning (normal driving,
traffic density mode, or special route state) and helper outputs still need
more call-path analysis. Do not label the `+0x3dc` field as speed or the
threshold branches as braking without further evidence.

### Empty-queue fallback (`0x4259c0` → `0x420b80` / `0x420a60`)

The callback's empty-route-count branch (`+0x6c == 0`) compares owner
`+0x42c` with `10.0` at `0x783c34`. When the value is below that threshold,
the callback gets a scalar from `0x404910` and passes it with the owner's
heading `+0x40c` and position/direction pointers at `+0x60/+0x70` to
`0x420a60`. At or above the threshold it calls `0x4259c0`, which builds a
different path input from the owner and controller.

`0x404910` reads the owner through controller `+0x04`, multiplies owner
`+0x430` by the value returned from the controller's vtable slot `+0x60`, and
returns the greater of that product and global float `125.0` at `0x7e8e60`.
The callback keeps this scalar separate from the heading and position
arguments passed to `0x420a60`.

`0x420a60` normalizes its supplied direction vector with `0x43cf10` when its
magnitude is at least `1.0` (`0x784250`). For a shorter vector it instead
derives a direction from the supplied 16-bit heading via `0x43c390` and clears
the temporary Z component. After pruning the queue through `0x41d160`, it
computes `step = scalar * 0.2` (`0x784554`) and appends five samples for
`i = 0..4` at
`origin + unitDirection * (i * step)`, using `0x43d0c0`. The first sample is
at the origin and the last is four steps away, so this generated path spans
`0.8 * scalar` along the chosen direction. Appended entries carry sentinel
route references rather than authored route IDs; the helper then clears queue
byte `+0x1c9`. This gives the low-threshold branch a concrete fallback
geometry and sampling interval.

`0x4259c0` reads the owner's 16-bit heading and reaches the controller through
owner `+0x2c`. It calls that controller's vtable slot `+0x60`, multiplies the
returned float by owner `+0x430`, and selects the greater of that product and
the global float at `0x7e8e60`. It passes this scalar, pointers to owner
`+0x60/+0x70/+0x80`, and the heading word to `0x420b80`. This is a bounded
speed-related path input; the virtual method's name and scalar units are not
recovered.

`0x420b80` first prunes the temporary path through `0x41d160`. It compares
`0.75 * lengthXY(owner+0x70)` with `lengthXY(owner+0x80)` using the constant
at `0x785510`. When the first value is smaller, it delegates to `0x420a60`
with the `owner+0x70` vector and the origin at `owner+0x60`. Otherwise, for a
positive scalar, it repeatedly builds points with two point-plus-vector calls
to `0x43d090`, using the `0.1` scale at `0x7837cc`, then appends them through
`0x404cd0`. This branch runs at most 20 iterations and stops when the queue
reaches its 20-point capacity, its accumulated planar path length reaches the
scalar, or its forward-alignment check fails. The append calls use sentinel
route references and a mode byte, so these generated points are fallback path
state rather than resolved `0x34121` route waypoints. The exact semantic role
of the two vectors is not established by this geometry test.

This establishes a useful edge case for recreation: an empty authored route
queue does not necessarily leave the AI without a target. Depending on the
owner's `+0x42c` value, the controller seeds a path from position/direction
inputs or builds a path through a speed-scaled controller value. The output
remains lookahead path state; these helpers do not themselves apply throttle
or steering forces.

## Confirmed corridor geometry nearby

The route evaluator at `0x0040df50` consumes waypoint extents at `+0x14/+0x16`
and converts each by `/256` before road-bound calculations. Direct calls to
that evaluator come from `0x0040e7c0` and `0x0042404b`; the lane action's
callback `0x004242d0` does not call it directly. Treat the corridor evaluator
as shared/adjacent AI route logic until a stronger call-path link is found.

## What is still missing for a faithful recreation

- Names and meanings of route modes 0, 1, and 2.
- How route candidates progress across frames and transitions, including the
  semantic meaning of the cursor fields and how the selector's chosen phase
  relates to lane changes. The `0x00420ed0` ranking, trial update, and
  successful commit path are traced; queue population continues through
  `0x00420d40` and `0x0041d530`.
- The producer and magnitude of actor vector `+0x70/+0x74`, whether it aliases
  the mover component's displacement vector, and the roles of the driver's
  three separate angle-control channels. The owner transform sync updates
  position delta at `+0x50/+0x54/+0x58` and copies transform data, but does not
  write `+0x70/+0x74`; see [TrafficMover](traffic-mover.md#branch-level-movement-and-motion-scalar-update)
  for this provenance boundary.
- The selected waypoint/lane offset and desired speed passed to
  `TrafficMover`, plus the meaning and units of actor fields used as speed
  inputs.
- Which runtime component consumes the intersection action's staged car tuple,
  and how its route-slot state maps to observed right-of-way behavior.

The companion [TrafficMover notes](traffic-mover.md) describe the lower-level
turn-limited controller and physics handoff. The recovered route coordinates,
waypoint extents, and route references are exported in
[`freeroam-traffic-routes.json`](freeroam-traffic-routes.json) and
[`freeroam-traffic-nodes.json`](freeroam-traffic-nodes.json).

## Driver factory and `TrafficTeleporter` bridge

The generic controller factory at `0x005eba30` accepts a numeric type and
switches over values 0 through 5. Type 2 allocates a `0x7d4`-byte object and
calls constructor `0x004299c0`. That constructor installs vtable
`0x00785518`, creates a `0x7a0`-byte child, and calls `0x00427f20` on it. The
constructor at `0x00427f20` installs vtable `0x00785118` and initializes an
embedded `RealDriver` at child offset `+0x470`. The nearby class-name strings
are `DragDriver` and `TrafficTeleporter`; the factory and constructor layout
support that parent/child association, though the string-to-vtable registry
record has not been decoded independently.

The child constructor receives the actor pointer as its argument; it passes
that same pointer to both the `APsychoDriver` base constructor and the
embedded `RealDriver` constructor. It then reads the pointer at `actor+0x30`
and copies three floats at source offsets `+0x214`, `+0x210`, and `+0x20c`
into the embedded `RealDriver` at matching offsets through setters
`0x005eb8f0`, `0x005eb900`, and `0x005eb910`. It also copies the child's own
values at `+0x684`, `+0x680`, and `+0x67c` through those setters. During the
type-2 mode change, the factory leaves the old `actor+0x30` pointer in place
while constructing the replacement, then publishes the new wrapper's child
there afterward. This means the new embedded driver is seeded from the
previous actor driver object's three output fields. In the embedded
`RealDriver` layout, those destinations are its longitudinal command slots:
`+0x20c` is throttle, `+0x210` is e-brake, and `+0x214` is brake, as traced
in [`player-controls.md`](player-controls.md). The source object's matching
values are not independently named by this transfer, and the construction
path does not prove that the source is `AIActionDriveTrafficLane` or the
ordinary free-roam traffic controller.

The `DragDriver` update at `0x004220a0` calls its `APsychoDriver` update
(`0x00421030`), updates the embedded `RealDriver` at `+0x470` through its
virtual update slot `+0x04`, then computes and stores three more values via
`0x005eb8f0`, `0x005eb900`, and `0x005eb910`. This confirms the wrapper runs
both the AI driver logic and a nested RealDriver tick in one update. The
fields are shared with the AI output tuple, but their physical control
meanings on the source side and their subsequent actor-side consumers are
still unresolved. Because the embedded RealDriver was constructed with the
actor as its host, its established `+0x438` host write is specifically an
actor-field write on this path; the field's use by the vehicle simulation is
still not identified.

The parent wrapper's serializer at `0x00429a70` serializes its child and
several parent fields. A generic mode-change routine at `0x005f3010` selects
factory type 2 when the current controller type at actor `+0x24` is not 2,
then follows the actor's `+0x30` pointer and calls virtual methods on that
object. The factory stores the new wrapper's `+0x08` child through this actor
field, so this is the newly published `DragDriver` in the type-2 case. This shows
the `TrafficTeleporter`/`DragDriver` family participates in runtime controller
mode changes. The contexts examined so far do not establish that this is the
regular traffic vehicle's route-following controller; keep it separate from
the proven `AICarController` → `APsychoDriver` lane-action path.

## `RaceCarController` event writes to the host command object

The serializer at `0x00429a70` passes the class-name string
`RaceCarController` to `0x00578ce0`. The adjacent routine at `0x00429b70`
takes its host actor from controller `+0x04`, then resolves the host's current
shared command object through actor `+0x30`. Its placement in the
`RaceCarController` code region associates it with that controller; its
shared-object writes themselves are directly confirmed. This is a
controller-side writer to the same three command slots that `PhysicsMover`
reads, not merely the constructor-time snapshot described above.

The routine takes an event code as its first stack argument and compares
global float `0x0089e7b8` with zero. When that float is positive and the event
code is `3`, it writes brake `0`, e-brake `0`, and throttle `0.5` to the host
command object, then updates host state and invokes the command object's
virtual slot `+0x1c` with a false flag. When the global float is nonpositive
and the event code is `4`, it writes brake `0`, e-brake `0`, and throttle
`1.0`, then invokes the same virtual slot with a false flag. Other code/sign
combinations take cleanup or callback paths without these explicit three
setter calls. The global's game-level meaning and the event-code names have
not been recovered.

This establishes that an alternate controller can set actor `+0x30` directly,
but it does not establish that `RaceCarController` drives streamed free-roam
traffic or that these event codes are part of ordinary lane-following. Keep
this path distinct from both `RealDriver` player input and
`AIActionDriveTrafficLane` until a caller or runtime mode transition connects
them.

For a WebGL reconstruction, this is a useful subsystem boundary: the route
action's target queue and the traffic mover's kinematic update are recovered,
while this alternate driver wrapper exposes a separate three-channel bridge
whose meanings and relationship to ordinary traffic remain open.

## Route 450 painted-surface profile, revision r117

A fresh query against the packaged renderer's source-triangle index sampled the
route-450 traffic waypoint cross-section in the decoded route basis. At this
waypoint the route origin is on `RDP_LANEA_YS_AA_KT` from lateral offsets
`-4.0` through `+3.5` world units. `RDP_LANEB_WD_AA_KT` occupies the sampled
outer bands on both sides (`-8.0..-4.5` and `+4.0..+10.0`). Both material
images are the original embedded RDP textures; the renderer generates no
marking geometry. The source traffic pose is therefore not guaranteed to be the
center of a drivable lane, even when its waypoint height correctly lands on
pavement.

This is a concrete reason to finish reproducing the Retail FuturePoint
selector before moving traffic sideways. Its selected phase and scalar weights
feed the `0x005dd490` waypoint transform described above, which can move a
center route reference onto a corridor selected from the authored width and
adjustment fields. The route-profile measurement does not identify the runtime
phase, weight values, or traffic side, so it is not used as a fixed offset.
The current close moving-body audit verifies upright body frames only on this
route; the route-wide inversion report and tire assembly remain unresolved.

## Source corridor sample construction, revision r145

The state-update caller at `0x00454fee` passes a resolved route descriptor
and a mode byte from `0x005b5330` into `0x00447dc0`. The caller's full runtime
role is not established. That routine builds a sequence of positions from the
descriptor's `+0x8c` waypoint records. Before each
`0x005dd490` point transform, it converts the greater of waypoint widths
`+0x14/+0x16` from `/256` world units, converts the selected signed adjustment
byte at `+0x22+selector` from `/128`, and multiplies the two. The resulting
source lateral contribution is therefore:

```text
max(widthA, widthB) / 256 * signed_adjustment[selector] / 128
```

`0x005d5cd0` may further shape that per-waypoint offset along the path using a
runtime descriptor. The descriptor state and selected mode are not available
in the static route export. The r146 viewer fallback evaluated both
adjustment banks (`2..9`) because it did not have the runtime FuturePoint
subtype. That was too broad: the disassembled `0x00415b70` path returns logical
phases `2..5`; subtype `0x00784a48` uses them directly, while subtype
`0x00784aa0` remaps them to bytes `6..9`. Revision r147 limits the preview
fallback to the direct `2..5` bank and still requires the candidate to land on
source-authored `RDP_LANEA` triangles. It applies that candidate to the player
and ambient traffic only when the raw point is on `RDP_ROADSTRIP_MC`. This is a
narrower decompilation-backed preview rule, not proof that every free-roam
actor uses the direct subtype or that retail selector weights have been
reproduced.

The r144 zero-offset browser capture placed L4RD route 102 directly on the
source-textured hatched median. In the r146 sample, direct phase selector 4
and alternate-bank selector 8 could both produce positive candidates; r147 no
longer treats the alternate bank as available without its subtype state. The
hatches are from the embedded
`RDP_ROADSTRIP_MC` image after the P8 red/blue channel conversion; no road
marking geometry is generated by the viewer.

## FuturePoint selector-bank correction, revision r147

The `0x00415b70` trace resolves the distinction that r146 blurred: logical
phases are `2..5`, and only the `0x00784aa0` FuturePoint subtype maps those
phase indexes to route adjustment bytes `6..9`. The `0x00784a48` subtype reads
bytes `2..5` directly. Since the static export has neither the live
FuturePoint vtable nor the actor's route mode, the viewer now evaluates only
the direct bank for its median fallback. Each candidate still has to hit an
exported `RDP_LANEA` surface, and the road's marks remain sampled from the
retail texture and UVs. This corrects the preview's unsupported selector
search; it does not resolve which subtype each retail free-roam car receives.

## Texture-gated street centering, revision r149

The L4RA route-101 browser view showed the route origin on the double-yellow
center marking of an ordinary two-way street, while the r147 lane fallback
only handled the hatched `RDP_ROADSTRIP_MC` median. The preview now samples the
embedded `RDP_LANE*` texture at lateral offsets around the route origin. It
applies a positive direct-bank corridor candidate only when the origin is near
a source-painted stripe, the candidate is clear of that stripe, and the
candidate remains on exported `RDP_LANEA` triangles. This moved the player off
the center marking in the captured L4RA curve and uses the same placement for
ambient traffic and collision checks. The line and paint remain texture
pixels; no line geometry is created. The trigger and positive-side choice are
still a preview rule, since the actor's live FuturePoint subtype and selected
phase are not included in the static exports.


## Traffic frame callback trace, revision r137

The selected-candidate update is a shared runtime operation, not a direct
move-to-route-center call. Routine `0x00428030` reads the actor pointer passed
by its caller and, when its cached candidate index is invalid, invokes
`0x004198c0` with the actor's position at `actor+0x60`, the controller selector
byte at `controller+0xad`, and the current candidate-list bounds. The selector
returns an index into the controller's FuturePoint pointer array. The callback
then calls `0x00419670` with the selected FuturePoint and actor, reads the
FuturePoint scalar at `+0x54`, and passes that scalar, the actor, and the
controller queue into `0x00422220` when the queue already has entries.

When the queue is empty, the callback instead calls `0x00426810`. That routine
uses the selected FuturePoint's route reference, calls `0x005d89b0` to resolve
a route position/frame, creates and appends a FuturePoint through
`0x00418a50`, resets the controller selector byte, and then advances the queue
through `0x00422220`. Disassembly shows this shared callback at callers
`0x00428a66`, `0x0042a337`, and `0x0042a58b`; the caller state differs, so this
trace alone does not classify all three as ordinary free-roam traffic.

This tightens the known call path but still does not identify the live selector
phase and scalar weights that `0x005dd490` applies to corridor widths. A fixed
texture-based lane shift remains a viewer approximation and must not be
represented as recovered retail behavior. The rendered road marks in the viewer
are source texture samples; `drawSceneBatches` contains no generated stripe
pass. The fresh L4RD screenshot in the review feed records that source-texture
result, including its remaining visual mismatch.

## WebGL route-frame correction, revision r137

The viewer previously normalized the interpolated route-forward and
route-lateral vectors independently. Unit vectors at either end of a segment
can be perpendicular while their interpolation is not; using that pair
directly can shear and roll a rendered car through a turn. The r137 transform
projects the lateral vector perpendicular to forward, derives up from their
cross product, enforces world-up on the city road plane, and rebuilds lateral
from forward and up. It still applies the per-body axis permutation and signs
measured from each retail BODY_A object transform. This is a frame-stability
correction, not a substitute for the unresolved FuturePoint lane selector or
retail traffic spawn selection.

## Waypoint transform callers and live corridor weighting, revision r142

The exact traffic executable recovered from the ISO was disassembled again at
the call sites of `0x005dd360` and `0x005dd490`. This narrows where the current
lane-placement gap lives.

`0x00447dc0` is called at `0x00454fee`. That caller passes a route/descriptor
pointer from its state at `+0x1c80` and a signed mode byte returned by
`0x005b5330`. Inside `0x00447dc0`, the mode and bits in the input object's
`+0x70` choose between two banks of selector-availability bytes. The loop
iterates waypoint records, takes `max(widthA,widthB)/256`, adds the selected
signed corridor byte at waypoint `+0x22+selector` scaled by a runtime float,
and can further adjust that scalar through `0x005d5cd0`. Depending on the
selected runtime subtype, it calls either the `0x005dd360` or
`0x005dd490` transform, then writes a 16-byte XYZ/scalar record into the
owner's output list. This establishes that the texture/material lane shift in
WebGL cannot be treated as the retail traffic rule.

The adjacent builder `0x005e3ea0` also walks two banks of fixed-size records.
For each entry it reads the two authored widths, chooses the larger width,
applies the per-selector corridor adjustment and a runtime weight, then calls
`0x005dd360` or `0x005dd490` according to the record subtype. The resulting
point is stored with its scalar alongside it. The disassembly proves the
selector-dependent width calculation and subtype dispatch; the values and
semantics of the runtime weight, `0x005d5cd0` result, and the caller's mode byte
are still unresolved. These are the next inputs to recover before replacing
the WebGL pavement-based lane correction.

The route-vector survey found no inverted authored frame: all 14,695 decoded
waypoint frames have a positive world-up component after the source
forward/lateral cross product (minimum 0.7098), and interpolated segment frames
retain the same lower bound. That rules out a source waypoint vector pointing
down as the general inversion cause. The reported upside-down cars remain
unlocalized; junction transitions now carry the interpolated 3D route frame,
but route-wide runtime body selection and transitions still need a reproducing
capture.

The new in-browser route probe exercised the L4RD route-102 to route-101
handoff at the decoded 1.06-unit gap. Across the 3D interpolation, the
forward/lateral cross product retained `up.z` between 0.9996 and 0.9999 and
the heading stayed aligned with both connected route directions. This verifies
the junction-frame code path numerically, but that player start produced zero
ambient traffic candidates, so it is not a live-car inversion reproduction.

## Static route selector boundary, revision r150 (2026-10-14)

The route export carries both the ten selector-adjustment bytes at each
waypoint and the route-header availability bytes at `route+0x82+selector`.
The preview now limits its direct-bank candidates to selectors 2–5 whose route
header byte is nonzero, matching the eligibility check in `0x0040f4a0`.
Alternate-bank selectors 6–9 remain excluded because their use depends on the
runtime FuturePoint subtype and actor mode.

This only constrains which selector can be considered. The retail resolver
adds a candidate-specific scalar and corridor metric to a stored FuturePoint
weight before constructing its final position. Those runtime candidate values
are not present in the exported `0x34121` route objects. The viewer's lane
hint therefore remains a texture/material-checked approximation; it must not
be described as the retail lateral position. No road-marking geometry is
created by this path: visible paint is sampled from the source RDP materials
and their UVs.
