chore(prototype): try jumping follower past leader via MovePath component exchange

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
haylanandClaude Bot committed 2026-10-05 22:55:36 +02:00
1 parent e77fabb772
commit 39f2c63667
1 file changed
+32 -4
+32 -4
View File
@@ -9,7 +9,9 @@ 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 = 0 -- rebuild disabled: proposals were silently rejected (segments kept their ids) 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 MAX_SETLINES = 0 -- set-line disabled: lanes ahead get re-planned by the engine anyway, no forced lane change
local MAX_JUMPS = 3 -- EXPERIMENT: debug component exchange of MovePath with pathPos moved past the leader
local JUMP_AHEAD = 15.0
local elapsed = 0.0 local elapsed = 0.0
local ticks = 0 local ticks = 0
@@ -33,6 +35,7 @@ end
local record Car local record Car
v : integer v : integer
pos : number pos : number
pos01 : number
speed : number speed : number
dir : boolean dir : boolean
end end
@@ -40,6 +43,7 @@ end
local record ProbeState local record ProbeState
rebuilds : integer rebuilds : integer
setlines : integer setlines : integer
jumps : 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
@@ -97,17 +101,31 @@ local function setLine(v : integer)
api.cmd.sendCommand(api.cmd.makeVehicleSetLineCmd(v, tv.line, tv.stopIndex)) api.cmd.sendCommand(api.cmd.makeVehicleSetLineCmd(v, tv.line, tv.stopIndex))
end end
-- returns false when the target doesn't fit on the current edge (we don't jump across edges)
local function jumpPast(follower : Car, leader : Car) : boolean, string
if leader.pos01 <= 0 then return false, "leader pos01=0" end
local len = leader.pos / leader.pos01
local target = leader.pos + JUMP_AHEAD
if target >= len then return false, string.format("target %.1f beyond edge length %.1f", target, len) end
local mp = api.engine.getComponent(follower.v, api.type.ComponentType.MOVE_PATH) as Engine.Component.MovePath
mp.dyn.pathPos.pos = target
mp.dyn.pathPos.pos01 = target / len
api.cmd.sendCommand(api.cmd.debug.makeComponentExchangeCmd(follower.v, mp as Engine.Component))
return true, string.format("pos %.1f -> %.1f (leader %.1f, edge length %.1f)", follower.pos, target, leader.pos, len)
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.setlines = st.setlines or 0
st.jumps = st.jumps 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 setlines=%d", ticks, tostring(dt), #vehicles, st.rebuilds, st.setlines)) p(string.format("tick=%d dt=%s playerVehicles=%d rebuilds=%d setlines=%d jumps=%d", ticks, tostring(dt), #vehicles, st.rebuilds, st.setlines, st.jumps))
local byLane : {string : {Car}} = {} local byLane : {string : {Car}} = {}
local laneSeg : {string : integer} = {} local laneSeg : {string : integer} = {}
@@ -128,12 +146,12 @@ local function dump(dt : number, state : GameScriptState<ProbeState>)
local key = seg .. ":" .. edge.index local key = seg .. ":" .. edge.index
byLane[key] = byLane[key] or {} byLane[key] = byLane[key] or {}
laneSeg[key] = seg laneSeg[key] = seg
table.insert(byLane[key], { v = v, pos = mp.dyn.pathPos.pos, speed = mp.dyn.speed, dir = dir }) table.insert(byLane[key], { v = v, pos = mp.dyn.pathPos.pos, pos01 = mp.dyn.pathPos.pos01, speed = mp.dyn.speed, dir = dir })
end end
end end
end end
if st.rebuilds >= MAX_REBUILDS and st.setlines >= MAX_SETLINES then return end if st.rebuilds >= MAX_REBUILDS and st.setlines >= MAX_SETLINES and st.jumps >= MAX_JUMPS 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
@@ -143,6 +161,16 @@ 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.jumps < MAX_JUMPS then
local ok, why = jumpPast(a, b)
p(string.format("JUMP_TEST seg=%d follower=%d leader=%d sent=%s %s", seg, a.v, b.v, tostring(ok), why))
if ok then
st.jumps = st.jumps + 1
st.watch = { a.v, b.v }
state:set(st)
return -- one experiment per dump
end
end
if st.setlines < MAX_SETLINES then if st.setlines < MAX_SETLINES then
local amp = api.engine.getComponent(a.v, api.type.ComponentType.MOVE_PATH) as Engine.Component.MovePath 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", p(string.format("SETLINE_TEST candidate seg=%d follower=%d (v=%.2f) leader=%d (v=%.2f) lanesAhead before: %s",