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
@@ -1,18 +1,14 @@
|
||||
-- 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.
|
||||
-- Every INTERVAL it dumps every player-owned road vehicle and groups them by street segment.
|
||||
-- EXPERIMENT: when a player vehicle is within MAX_GAP behind another in the same lane of a two-way segment,
|
||||
-- rebuild that segment (remove + re-add an identical BaseEdge) and log where both vehicles end up.
|
||||
-- Engine thread: every INTERVAL it logs every player-owned road vehicle (segment, lane, position, speed).
|
||||
-- GUI thread: EXPERIMENT flipping the positions of a follower and its leader (see below).
|
||||
-- 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).
|
||||
|
||||
local TAG = "[overtake_probe] "
|
||||
local INTERVAL = 2.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 ticks = 0
|
||||
|
||||
@@ -41,170 +37,23 @@ local record Car
|
||||
dir : boolean
|
||||
end
|
||||
|
||||
local record ProbeState
|
||||
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 function dump(dt : number)
|
||||
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 jumps=%d", ticks, tostring(dt), #vehicles, st.rebuilds, st.setlines, st.jumps))
|
||||
|
||||
local byLane : {string : {Car}} = {}
|
||||
local laneSeg : {string : integer} = {}
|
||||
p(string.format("tick=%d dt=%s playerVehicles=%d", ticks, tostring(dt), #vehicles))
|
||||
for _, v in ipairs(vehicles) do
|
||||
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 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
|
||||
local edge, dir = edgeAt(mp)
|
||||
local seg = edge and edge.entity or -1
|
||||
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",
|
||||
watched[v] and "WATCH " or "", v, seg, tostring(edge and edge.index), tostring(dir), tostring(edge and turnaround(edge)),
|
||||
"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",
|
||||
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),
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
-- 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)
|
||||
end
|
||||
|
||||
local ret : GameScriptWithGui<ProbeState, GuiState> = {
|
||||
update = function(_userParams : table, state : GameScriptState<ProbeState>, dt : number) : nil
|
||||
local ret : GameScriptWithGui<table, GuiState> = {
|
||||
update = function(_userParams : table, _state : GameScriptState<table>, dt : number) : nil
|
||||
ticks = ticks + 1
|
||||
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 elapsed < INTERVAL then return end
|
||||
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,
|
||||
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)
|
||||
end,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user