From 39f2c63667fa22ba99c358469d77c261b6d670c7 Mon Sep 17 00:00:00 2001 From: ArthurErlich Date: Mon, 5 Oct 2026 22:55:36 +0200 Subject: [PATCH] chore(prototype): try jumping follower past leader via MovePath component exchange Co-Authored-By: Claude Opus 5.5 --- mod/content/overtake_probe.script.tl | 36 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/mod/content/overtake_probe.script.tl b/mod/content/overtake_probe.script.tl index a5801eb..63a99bb 100644 --- a/mod/content/overtake_probe.script.tl +++ b/mod/content/overtake_probe.script.tl @@ -9,7 +9,9 @@ local TAG = "[overtake_probe] " local INTERVAL = 2.0 local MAX_GAP = 40.0 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 ticks = 0 @@ -33,6 +35,7 @@ end local record Car v : integer pos : number + pos01 : number speed : number dir : boolean end @@ -40,6 +43,7 @@ end local record ProbeState rebuilds : integer setlines : integer + jumps : integer watch : {integer} -- vehicles involved in the last rebuild, flagged in later dumps end @@ -97,17 +101,31 @@ local function setLine(v : integer) api.cmd.sendCommand(api.cmd.makeVehicleSetLineCmd(v, tv.line, tv.stopIndex)) 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) local st = state:get() st.rebuilds = st.rebuilds or 0 st.setlines = st.setlines or 0 + st.jumps = st.jumps 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 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 laneSeg : {string : integer} = {} @@ -128,12 +146,12 @@ local function dump(dt : number, state : GameScriptState) local key = seg .. ":" .. edge.index byLane[key] = byLane[key] or {} 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 - 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 i = 1, #cars do for j = 1, #cars do @@ -143,6 +161,16 @@ 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.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 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",