diff --git a/docs/research/vehicle-probe.md b/docs/research/vehicle-probe.md index ff135e6..a793923 100644 --- a/docs/research/vehicle-probe.md +++ b/docs/research/vehicle-probe.md @@ -75,3 +75,8 @@ Scanned: the 9 mods in `C:\Users\Public\mod.io\10640\mods\` (e.g. `devers_1`, `m - Likely cause (human's hypothesis): the vehicles' `MovePath` refers to lanes of the removed segment, so the engine must re-attach them to the new segment and re-plan, and a vehicle can land in a different lane. - **Debug `MovePath` exchange is unusable from engine state**: `api.cmd.sendCommand(api.cmd.debug.makeComponentExchangeCmd(v, movePath))` fails with `Error decoding argument #3` even **without** a callback. So the earlier error wasn't caused by the callback. The only untried route is the GUI thread. - Not yet tested: a rebuild on a two-way (one lane per direction) road. + +## Rebuild on a two-way road (run 2026-10-05 ~21:12 UTC) + +- 3 `replaceSegment` rebuilds on `country_new_small` (one lane per direction), segments 30213, 29776 and 30215, follower 25.8 m behind leader. **No effect**: the next snapshot had the same gap (22.6/48.4 → 49.3/75.1), both vehicles stayed in lane 2, same speeds, same order for 10+ snapshots. Rebuilding does not move vehicles past each other on single-lane roads. +- How the multilane lane change happens is still uncaught: snapshots every 2 s only show before (both lane 4) and after (the leader in lane 3 two segments later). The probe now traces the two vehicles on every update for ~30 s after a rebuild (`TRACE` lines), and rebuilds only multilane segments. diff --git a/mod/content/overtake_probe.script.tl b/mod/content/overtake_probe.script.tl index d103d60..455a655 100644 --- a/mod/content/overtake_probe.script.tl +++ b/mod/content/overtake_probe.script.tl @@ -12,6 +12,7 @@ local MAX_REBUILDS = 3 -- hand-built proposals were silently rejected; now via a local MAX_SETLINES = 0 -- set-line disabled: lanes ahead get re-planned by the engine anyway, no forced lane change local MAX_JUMPS = 0 -- dead: sendCommand(makeComponentExchangeCmd(...)) fails in engine state even without a callback local JUMP_AHEAD = 15.0 +local WATCH_TICKS = 150 -- ~30 s of game time at dt=0.2: log watched vehicles every update after a rebuild local elapsed = 0.0 local ticks = 0 @@ -45,6 +46,7 @@ local record ProbeState setlines : integer jumps : integer watch : {integer} -- vehicles involved in the last rebuild, flagged in later dumps + watchLeft : integer -- updates left for per-tick logging of the watched vehicles end -- No edge objects (stops, signals), @@ -61,7 +63,8 @@ local function rebuildable(seg : integer) : boolean, string if on and (tonumber(tostring(k)) or 0) >= 2 then driving = driving + 1 break end end end - -- any lane count: what a rebuild does to vehicles on the segment doesn't depend on it + -- one lane per direction: 3 rebuilds there changed nothing (same gap, same lane), so trace multilane only + if driving < 4 then return false, "driving lanes=" .. driving .. " (single-lane rebuilds showed no effect)" end return true, string.format("drivingLanes=%d template=%s length=%.1f", driving, tostring(be.roadTemplate), be.distance) end @@ -176,6 +179,7 @@ local function dump(dt : number, state : GameScriptState) rebuild(seg) st.rebuilds = st.rebuilds + 1 st.watch = { a.v, b.v } + st.watchLeft = WATCH_TICKS state:set(st) p(string.format("REBUILD_TEST sent #%d seg=%d", st.rebuilds, seg)) return -- one rebuild per dump @@ -186,10 +190,28 @@ local function dump(dt : number, state : GameScriptState) end end +-- per-update trace of the watched vehicles, to catch the moment a lane changes +local function trace(state : GameScriptState) + local st = state:get() + if (st.watchLeft or 0) <= 0 or st.watch == nil then return end + for _, v in ipairs(st.watch) do + local mp = api.engine.getComponent(v, api.type.ComponentType.MOVE_PATH) as Engine.Component.MovePath + if mp ~= nil then + local edge, _dir = edgeAt(mp) + p(string.format("TRACE t=%d veh=%d seg=%s lane=%s pos=%.1f v=%.2f ahead=%s", + WATCH_TICKS - st.watchLeft, v, tostring(edge and edge.entity), tostring(edge and edge.index), + mp.dyn.pathPos.pos, mp.dyn.speed, lanesAhead(mp))) + end + end + st.watchLeft = st.watchLeft - 1 + state:set(st) +end + local ret : GameScript = { update = function(_userParams : table, state : GameScriptState, dt : number) : nil ticks = ticks + 1 elapsed = elapsed + dt + if dt > 0 then xpcall(trace, function(err : any) p("ERROR trace " .. tostring(err)) end, state) end if ticks == 1 then p("update running, first dt=" .. tostring(dt)) end if elapsed < INTERVAL then return end elapsed = 0.0