From e77fabb772b3e993e6f883e8793e9c3a025c3288 Mon Sep 17 00:00:00 2001 From: ArthurErlich Date: Mon, 5 Oct 2026 22:51:26 +0200 Subject: [PATCH] chore(prototype): try re-path via makeVehicleSetLineCmd; record failed rebuild Co-Authored-By: Claude Opus 5.5 --- docs/research/vehicle-probe.md | 6 +++++ mod/content/overtake_probe.script.tl | 38 +++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/docs/research/vehicle-probe.md b/docs/research/vehicle-probe.md index 23b45e9..4319cab 100644 --- a/docs/research/vehicle-probe.md +++ b/docs/research/vehicle-probe.md @@ -46,3 +46,9 @@ Ticket "Probe vehicle data and native passing in game" (part of map "Map: TpF3 o ## Decision Overtaking on two-way roads (one lane per direction) is dropped (decided by the user, 2026-10-05). The effort continues on multilane roads only: a held-back vehicle should switch to a lane the slower vehicle isn't in. + +## Segment rebuild (ticket "Can rebuilding a road segment move a passer past its blocker?") + +- The script sent 3 `makeWorldBuildProposalCmd` proposals (remove segment + re-add a copy of its `BaseEdge`/`BaseEdgeStreet`, `ignoreErrors = true`) for segments 29701 and 29699 (`country_new_medium`). **None took effect**: both segment ids stayed in use by vehicles until the end of the log, and 29699 was "rebuilt" twice. The game logged no error, and there's no callback in engine state. The copied `BaseEdge.distance` read `0.0`, so the copy is probably incomplete. A next attempt would need `api.engine.util.proposal.makeProposalData` to see the proposal's errors. +- The human's manual segment deletion left no trace in the probe data: every vehicle stayed `tvState=1` (en route), with only normal path trimming. +- Related find: `api.cmd.makeVehicleSendToDepotCmd(vehicle, sellOnArrival, jumpToDepotEntity)` "teleports" a vehicle to a depot. The engine can relocate vehicles instantly, but only to depots. diff --git a/mod/content/overtake_probe.script.tl b/mod/content/overtake_probe.script.tl index 96cf774..a5801eb 100644 --- a/mod/content/overtake_probe.script.tl +++ b/mod/content/overtake_probe.script.tl @@ -8,7 +8,8 @@ local TAG = "[overtake_probe] " local INTERVAL = 2.0 local MAX_GAP = 40.0 -local MAX_REBUILDS = 3 -- per save; counted in GameScriptState because module locals don't persist +local MAX_REBUILDS = 0 -- rebuild disabled: proposals were silently rejected (segments kept their ids) +local MAX_SETLINES = 5 -- EXPERIMENT: re-assign the follower to its own line + current stop to force a re-path local elapsed = 0.0 local ticks = 0 @@ -38,6 +39,7 @@ end local record ProbeState rebuilds : integer + setlines : integer watch : {integer} -- vehicles involved in the last rebuild, flagged in later dumps end @@ -78,16 +80,34 @@ local function rebuild(seg : integer) api.cmd.sendCommand(api.cmd.makeWorldBuildProposalCmd(proposal, nil, true, false)) end +-- lanes of the next few path edges, to see whether a re-path changes the lane choice +local function lanesAhead(mp : Engine.Component.MovePath) : string + local out : {string} = {} + local edges = mp.path.edges as {any} + for k = mp.dyn.pathPos.edgeIndex + 1, math.min(#edges, mp.dyn.pathPos.edgeIndex + 4) do + local e = edges[k] as UtilPathfinding.EdgeIdDir + table.insert(out, e.edgeId.entity .. ":" .. e.edgeId.index) + end + return table.concat(out, " ") +end + +local function setLine(v : integer) + local tv = api.engine.getComponent(v, api.type.ComponentType.TRANSPORT_VEHICLE) as Engine.Component.TransportVehicle + p(string.format("SETLINE_TEST send veh=%d line=%s stopIndex=%s", v, tostring(tv.line), tostring(tv.stopIndex))) + api.cmd.sendCommand(api.cmd.makeVehicleSetLineCmd(v, tv.line, tv.stopIndex)) +end + local function dump(dt : number, state : GameScriptState) local st = state:get() st.rebuilds = st.rebuilds or 0 + st.setlines = st.setlines or 0 st.watch = st.watch or {} local watched : {integer : boolean} = {} for _, w in ipairs(st.watch) do watched[w] = true end local player = api.engine.util.getPlayer() local vehicles = api.engine.getEntitiesWithComponent(api.type.ComponentType.TRANSPORT_VEHICLE, { requireOwnedByPlayer = player }) - p(string.format("tick=%d dt=%s playerVehicles=%d rebuilds=%d", ticks, tostring(dt), #vehicles, st.rebuilds)) + p(string.format("tick=%d dt=%s playerVehicles=%d rebuilds=%d setlines=%d", ticks, tostring(dt), #vehicles, st.rebuilds, st.setlines)) local byLane : {string : {Car}} = {} local laneSeg : {string : integer} = {} @@ -103,6 +123,7 @@ local function dump(dt : number, state : GameScriptState) watched[v] and "WATCH " or "", v, seg, tostring(edge and edge.index), tostring(dir), tostring(edge and turnaround(edge)), mp.dyn.pathPos.edgeIndex, #(mp.path.edges as {any}), mp.dyn.pathPos.pos, mp.dyn.speed, tostring(mp.state), tostring(tv and tv.state), tostring(tv and tv.line), mp.dyn.timeStanding)) + if watched[v] then p("WATCH lanesAhead veh=" .. v .. " " .. lanesAhead(mp)) end if edge ~= nil then local key = seg .. ":" .. edge.index byLane[key] = byLane[key] or {} @@ -112,7 +133,7 @@ local function dump(dt : number, state : GameScriptState) end end - if st.rebuilds >= MAX_REBUILDS then return end + if st.rebuilds >= MAX_REBUILDS and st.setlines >= MAX_SETLINES then return end for key, cars in pairs(byLane) do for i = 1, #cars do for j = 1, #cars do @@ -122,6 +143,17 @@ local function dump(dt : number, state : GameScriptState) -- leader must be moving, otherwise it's a queue at a stop or junction, not a slow vehicle if i ~= j and ahead and math.abs(b.pos - a.pos) < MAX_GAP and b.speed > 1.0 then local seg = laneSeg[key] + if st.setlines < MAX_SETLINES then + local amp = api.engine.getComponent(a.v, api.type.ComponentType.MOVE_PATH) as Engine.Component.MovePath + p(string.format("SETLINE_TEST candidate seg=%d follower=%d (v=%.2f) leader=%d (v=%.2f) lanesAhead before: %s", + seg, a.v, a.speed, b.v, b.speed, lanesAhead(amp))) + setLine(a.v) + st.setlines = st.setlines + 1 + st.watch = { a.v, b.v } + state:set(st) + return -- one experiment per dump + end + if st.rebuilds >= MAX_REBUILDS then return end local ok, why = rebuildable(seg) p(string.format("REBUILD_TEST candidate seg=%d follower=%d (pos=%.1f v=%.2f) leader=%d (pos=%.1f v=%.2f) ok=%s %s", seg, a.v, a.pos, a.speed, b.v, b.pos, b.speed, tostring(ok), why))