chore(prototype): try re-path via makeVehicleSetLineCmd; record failed rebuild

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
haylanandClaude Bot committed 2026-10-05 22:51:26 +02:00
1 parent ea23aa1dc5
commit e77fabb772
2 files changed
+41 -3

No files matched your search

+6
View File
@@ -46,3 +46,9 @@ Ticket "Probe vehicle data and native passing in game" (part of map "Map: TpF3 o
## Decision ## 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. 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.
+35 -3
View File
@@ -8,7 +8,8 @@
local TAG = "[overtake_probe] " local TAG = "[overtake_probe] "
local INTERVAL = 2.0 local INTERVAL = 2.0
local MAX_GAP = 40.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 elapsed = 0.0
local ticks = 0 local ticks = 0
@@ -38,6 +39,7 @@ end
local record ProbeState local record ProbeState
rebuilds : integer rebuilds : integer
setlines : integer
watch : {integer} -- vehicles involved in the last rebuild, flagged in later dumps watch : {integer} -- vehicles involved in the last rebuild, flagged in later dumps
end end
@@ -78,16 +80,34 @@ local function rebuild(seg : integer)
api.cmd.sendCommand(api.cmd.makeWorldBuildProposalCmd(proposal, nil, true, false)) api.cmd.sendCommand(api.cmd.makeWorldBuildProposalCmd(proposal, nil, true, false))
end 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<ProbeState>) local function dump(dt : number, state : GameScriptState<ProbeState>)
local st = state:get() local st = state:get()
st.rebuilds = st.rebuilds or 0 st.rebuilds = st.rebuilds or 0
st.setlines = st.setlines or 0
st.watch = st.watch or {} st.watch = st.watch or {}
local watched : {integer : boolean} = {} local watched : {integer : boolean} = {}
for _, w in ipairs(st.watch) do watched[w] = true end for _, w in ipairs(st.watch) do watched[w] = true end
local player = api.engine.util.getPlayer() local player = api.engine.util.getPlayer()
local vehicles = api.engine.getEntitiesWithComponent(api.type.ComponentType.TRANSPORT_VEHICLE, { requireOwnedByPlayer = player }) 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 byLane : {string : {Car}} = {}
local laneSeg : {string : integer} = {} local laneSeg : {string : integer} = {}
@@ -103,6 +123,7 @@ local function dump(dt : number, state : GameScriptState<ProbeState>)
watched[v] and "WATCH " or "", v, seg, tostring(edge and edge.index), tostring(dir), tostring(edge and turnaround(edge)), 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), 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)) 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 if edge ~= nil then
local key = seg .. ":" .. edge.index local key = seg .. ":" .. edge.index
byLane[key] = byLane[key] or {} byLane[key] = byLane[key] or {}
@@ -112,7 +133,7 @@ local function dump(dt : number, state : GameScriptState<ProbeState>)
end end
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 key, cars in pairs(byLane) do
for i = 1, #cars do for i = 1, #cars do
for j = 1, #cars do for j = 1, #cars do
@@ -122,6 +143,17 @@ local function dump(dt : number, state : GameScriptState<ProbeState>)
-- leader must be moving, otherwise it's a queue at a stop or junction, not a slow vehicle -- 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 if i ~= j and ahead and math.abs(b.pos - a.pos) < MAX_GAP and b.speed > 1.0 then
local seg = laneSeg[key] 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) 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", 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)) seg, a.v, a.pos, a.speed, b.v, b.pos, b.speed, tostring(ok), why))