chore(prototype): remove segment rebuild and dead engine-side experiments
Rebuild confirmed ineffective on single-lane roads (log + in game). Engine side is logging only again; the GUI-thread position flip is the one experiment left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5d3a254e55
commit
bfe5a22d1d
2 files changed
+16
-164
No files matched your search
@@ -80,3 +80,7 @@ Scanned: the 9 mods in `C:\Users\Public\mod.io\10640\mods\` (e.g. `devers_1`, `m
|
|||||||
|
|
||||||
- 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.
|
- 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.
|
- 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.
|
||||||
|
|
||||||
|
## Segment rebuild dropped (2026-10-05)
|
||||||
|
|
||||||
|
The human confirmed in game what the log showed: rebuilding the segment under a held-back vehicle does not get it past the blocker on a single-lane road. The rebuild code is removed from the probe (it's still in git history, last in commit 5d3a254). The lane split after rebuilds on multilane roads (see above) is left as an observation for the multilane ticket, not pursued here.
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
-- PROTOTYPE (tickets "Probe vehicle data and native passing in game", "Can rebuilding a road segment move a passer
|
-- PROTOTYPE (tickets "Probe vehicle data and native passing in game", "Can rebuilding a road segment move a passer
|
||||||
-- past its blocker?"): throwaway, lives only on branch prototype/vehicle-probe.
|
-- past its blocker?"): throwaway, lives only on branch prototype/vehicle-probe.
|
||||||
-- Every INTERVAL it dumps every player-owned road vehicle and groups them by street segment.
|
-- Engine thread: every INTERVAL it logs every player-owned road vehicle (segment, lane, position, speed).
|
||||||
-- EXPERIMENT: when a player vehicle is within MAX_GAP behind another in the same lane of a two-way segment,
|
-- GUI thread: EXPERIMENT flipping the positions of a follower and its leader (see below).
|
||||||
-- rebuild that segment (remove + re-add an identical BaseEdge) and log where both vehicles end up.
|
-- Earlier experiments (timeToIgnore, engine-side jump, set-line, segment rebuild) are in git history and
|
||||||
|
-- docs/research/vehicle-probe.md; none moved a vehicle past another on a single-lane road.
|
||||||
-- ponytail: module-local timer, not GameScriptState; fine for a probe (state resets between calls anyway).
|
-- ponytail: module-local timer, not GameScriptState; fine for a probe (state resets between calls anyway).
|
||||||
|
|
||||||
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 = 0 -- off while testing the GUI-thread position flip (replaceSegment works, see vehicle-probe.md)
|
|
||||||
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 elapsed = 0.0
|
||||||
local ticks = 0
|
local ticks = 0
|
||||||
|
|
||||||
@@ -41,170 +37,23 @@ local record Car
|
|||||||
dir : boolean
|
dir : boolean
|
||||||
end
|
end
|
||||||
|
|
||||||
local record ProbeState
|
local function dump(dt : number)
|
||||||
rebuilds : integer
|
|
||||||
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),
|
|
||||||
-- because re-adding them is out of scope for this probe.
|
|
||||||
local function rebuildable(seg : integer) : boolean, string
|
|
||||||
local be = api.engine.getComponent(seg, api.type.ComponentType.BASE_EDGE) as Engine.Component.BaseEdge
|
|
||||||
if be == nil then return false, "no BaseEdge" end
|
|
||||||
if #be.objects > 0 then return false, "has edge objects" end
|
|
||||||
-- count BaseEdge lanes, not TransportNetwork edges (town roads split each lane into several edges).
|
|
||||||
-- Sidewalks carry modes 0/1 only; driving lanes carry 2+ (observed: [2,3,4]).
|
|
||||||
local driving = 0
|
|
||||||
for _, lc in ipairs(be.laneConfigs) do
|
|
||||||
for k, on in pairs(lc.transportModes) do
|
|
||||||
if on and (tonumber(tostring(k)) or 0) >= 2 then driving = driving + 1 break end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
local function rebuild(seg : integer)
|
|
||||||
-- same call the official campaign (mission 03 init.script.tl) uses to refresh a road segment in place
|
|
||||||
local proposal = api.engine.util.proposal.replaceSegment(seg)
|
|
||||||
api.cmd.sendCommand(api.cmd.makeWorldBuildProposalCmd(proposal, nil, true, true))
|
|
||||||
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
|
|
||||||
|
|
||||||
-- 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 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 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 jumps=%d", ticks, tostring(dt), #vehicles, st.rebuilds, st.setlines, st.jumps))
|
p(string.format("tick=%d dt=%s playerVehicles=%d", ticks, tostring(dt), #vehicles))
|
||||||
|
|
||||||
local byLane : {string : {Car}} = {}
|
|
||||||
local laneSeg : {string : integer} = {}
|
|
||||||
for _, v in ipairs(vehicles) do
|
for _, v in ipairs(vehicles) do
|
||||||
local lv = api.engine.getComponent(v, api.type.ComponentType.LAND_VEHICLE) as Engine.Component.LandVehicle
|
local lv = api.engine.getComponent(v, api.type.ComponentType.LAND_VEHICLE) as Engine.Component.LandVehicle
|
||||||
local mp = api.engine.getComponent(v, api.type.ComponentType.MOVE_PATH) as Engine.Component.MovePath
|
local mp = api.engine.getComponent(v, api.type.ComponentType.MOVE_PATH) as Engine.Component.MovePath
|
||||||
local tv = api.engine.getComponent(v, api.type.ComponentType.TRANSPORT_VEHICLE) as Engine.Component.TransportVehicle
|
local tv = api.engine.getComponent(v, api.type.ComponentType.TRANSPORT_VEHICLE) as Engine.Component.TransportVehicle
|
||||||
if lv ~= nil and not lv.isTram and mp ~= nil then
|
if lv ~= nil and not lv.isTram and mp ~= nil then
|
||||||
local edge, dir = edgeAt(mp)
|
local edge, dir = edgeAt(mp)
|
||||||
local seg = edge and edge.entity or -1
|
|
||||||
p(string.format(
|
p(string.format(
|
||||||
"%sveh=%d seg=%d lane=%s dir=%s turnaround=%s edgeIndex=%d/%d pos=%.1f speed=%.2f state=%s tvState=%s line=%s standing=%.1f",
|
"veh=%d seg=%d lane=%s dir=%s turnaround=%s edgeIndex=%d/%d pos=%.1f speed=%.2f state=%s tvState=%s line=%s standing=%.1f",
|
||||||
watched[v] and "WATCH " or "", v, seg, tostring(edge and edge.index), tostring(dir), tostring(edge and turnaround(edge)),
|
v, edge and edge.entity or -1, 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
|
|
||||||
local key = seg .. ":" .. edge.index
|
|
||||||
byLane[key] = byLane[key] or {}
|
|
||||||
laneSeg[key] = seg
|
|
||||||
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
|
||||||
|
|
||||||
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
|
|
||||||
local a, b = cars[i], cars[j]
|
|
||||||
-- ponytail: assumes pos grows in driving direction when dir=true (held so far in the logs)
|
|
||||||
local ahead = (a.dir and b.pos > a.pos) or (not a.dir and b.pos < a.pos)
|
|
||||||
-- 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",
|
|
||||||
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))
|
|
||||||
if ok then
|
|
||||||
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
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- per-update trace of the watched vehicles, to catch the moment a lane changes
|
|
||||||
local function trace(state : GameScriptState<ProbeState>)
|
|
||||||
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
|
end
|
||||||
|
|
||||||
-- EXPERIMENT (GUI thread, where sendCommand callbacks work): flip the positions of a follower and its leader by
|
-- EXPERIMENT (GUI thread, where sendCommand callbacks work): flip the positions of a follower and its leader by
|
||||||
@@ -294,17 +143,16 @@ local function guiStep(gsState : GameScriptState<GuiState>)
|
|||||||
gsState:set(gs)
|
gsState:set(gs)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ret : GameScriptWithGui<ProbeState, GuiState> = {
|
local ret : GameScriptWithGui<table, GuiState> = {
|
||||||
update = function(_userParams : table, state : GameScriptState<ProbeState>, dt : number) : nil
|
update = function(_userParams : table, _state : GameScriptState<table>, dt : number) : nil
|
||||||
ticks = ticks + 1
|
ticks = ticks + 1
|
||||||
elapsed = elapsed + dt
|
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 ticks == 1 then p("update running, first dt=" .. tostring(dt)) end
|
||||||
if elapsed < INTERVAL then return end
|
if elapsed < INTERVAL then return end
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
xpcall(dump, function(err : any) p("ERROR " .. tostring(err)) end, dt, state)
|
xpcall(dump, function(err : any) p("ERROR " .. tostring(err)) end, dt)
|
||||||
end,
|
end,
|
||||||
guiUpdate = function(_userParams : table, _state : GameScriptStateReadOnly<ProbeState>, guiState : GameScriptState<GuiState>)
|
guiUpdate = function(_userParams : table, _state : GameScriptStateReadOnly<table>, guiState : GameScriptState<GuiState>)
|
||||||
xpcall(guiStep, function(err : any) p("ERROR gui " .. tostring(err)) end, guiState)
|
xpcall(guiStep, function(err : any) p("ERROR gui " .. tostring(err)) end, guiState)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user