chore(prototype): rebuild two-way segment under a held-back vehicle
Replaces the timeToIgnore experiment (still in history) with the segment-rebuild probe for "Can rebuilding a road segment move a passer past its blocker?". Capped at 3 rebuilds per save via GameScriptState. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e76dc453a
commit
dcc4b4afae
1 file changed
+81
-43
@@ -1,10 +1,14 @@
|
||||
-- PROTOTYPE (ticket "Probe vehicle data and native passing in game"): throwaway, lives only on branch prototype/vehicle-probe.
|
||||
-- Every INTERVAL it dumps every player-owned road vehicle, then groups them by street segment
|
||||
-- so lanes (edge index) and oncoming traffic (opposite direction / turnaround edge) are visible in stdout.txt.
|
||||
-- ponytail: module-local timer, not GameScriptState; fine for a probe, resets on reload.
|
||||
-- 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.
|
||||
-- ponytail: module-local timer, not GameScriptState; fine for a probe (state resets between calls anyway).
|
||||
|
||||
local TAG = "[overtake_probe] "
|
||||
local INTERVAL = 5.0
|
||||
local MAX_GAP = 40.0
|
||||
local MAX_REBUILDS = 3 -- per save; counted in GameScriptState because module locals don't persist
|
||||
local elapsed = 0.0
|
||||
local ticks = 0
|
||||
|
||||
@@ -25,86 +29,120 @@ local function turnaround(edge : EdgeId) : integer
|
||||
return tn.turnaroundEdges[edge.index + 1]
|
||||
end
|
||||
|
||||
-- EXPERIMENT: does api.cmd.debug.makeComponentExchangeCmd accept a MovePath, and does a positive
|
||||
-- dyn.timeToIgnore make the follower ignore the vehicle ahead? Hypothesis only; nothing documents either.
|
||||
local IGNORE_SECONDS = 10.0
|
||||
local MAX_GAP = 40.0
|
||||
|
||||
local record Car
|
||||
v : integer
|
||||
pos : number
|
||||
speed : number
|
||||
dir : boolean
|
||||
mp : Engine.Component.MovePath
|
||||
end
|
||||
|
||||
local function tryIgnore(follower : Car, leader : Car)
|
||||
local mp = follower.mp
|
||||
mp.dyn.timeToIgnore = IGNORE_SECONDS
|
||||
p(string.format("IGNORE_TEST send veh=%d leader=%d gap=%.1f v=%.2f leaderV=%.2f", follower.v, leader.v, math.abs(leader.pos - follower.pos), follower.speed, leader.speed))
|
||||
local cmd = api.cmd.debug.makeComponentExchangeCmd(follower.v, mp as Engine.Component)
|
||||
-- engine-state scripts can't pass a callback ("Error decoding argument #3"); the next dump shows the effect
|
||||
api.cmd.sendCommand(cmd)
|
||||
local record ProbeState
|
||||
rebuilds : integer
|
||||
watch : {integer} -- vehicles involved in the last rebuild, flagged in later dumps
|
||||
end
|
||||
|
||||
local function dump(dt : number)
|
||||
-- Two-way = exactly two driving lanes (modes other than the sidewalk ones). 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
|
||||
local tn = api.engine.getComponent(seg, api.type.ComponentType.TRANSPORT_NETWORK) as Engine.Component.TransportNetwork
|
||||
local driving = 0
|
||||
for _, e in ipairs(tn.edges) do
|
||||
if e.speedLimit > 0 then driving = driving + 1 end
|
||||
end
|
||||
if driving ~= 2 then return false, "driving lanes=" .. driving end
|
||||
return true, string.format("template=%s length=%.1f", tostring(be.roadTemplate), be.distance)
|
||||
end
|
||||
|
||||
local function rebuild(seg : integer)
|
||||
local be = api.engine.getComponent(seg, api.type.ComponentType.BASE_EDGE) as Engine.Component.BaseEdge
|
||||
local bes = api.engine.getComponent(seg, api.type.ComponentType.BASE_EDGE_STREET) as Engine.Component.BaseEdgeStreet
|
||||
local owned = api.engine.getComponent(seg, api.type.ComponentType.PLAYER_OWNED) as Engine.Component.PlayerOwned
|
||||
|
||||
local sae = api.type.SegmentAndEntity.new()
|
||||
sae.entity = -1
|
||||
sae.type = 0
|
||||
sae.comp = be
|
||||
sae.streetEdge = bes
|
||||
if owned ~= nil then sae.playerOwned = owned end
|
||||
|
||||
local proposal = api.type.SimpleProposal.new()
|
||||
proposal.streetProposal.edgesToRemove[1] = seg
|
||||
proposal.streetProposal.edgesToAdd[1] = sae
|
||||
-- engine-state scripts can't pass a callback; the next dump shows the effect
|
||||
api.cmd.sendCommand(api.cmd.makeWorldBuildProposalCmd(proposal, nil, true, false))
|
||||
end
|
||||
|
||||
local function dump(dt : number, state : GameScriptState<ProbeState>)
|
||||
local st = state:get()
|
||||
st.rebuilds = st.rebuilds 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", ticks, tostring(dt), #vehicles))
|
||||
p(string.format("tick=%d dt=%s playerVehicles=%d rebuilds=%d", ticks, tostring(dt), #vehicles, st.rebuilds))
|
||||
|
||||
local bySegment : {integer : {string}} = {}
|
||||
local byLane : {string : {Car}} = {}
|
||||
local laneSeg : {string : integer} = {}
|
||||
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 info = api.engine.system.landVehicleMoveSystem.getVehicleInfo(v)
|
||||
local seg = edge and edge.entity or -1
|
||||
local line = string.format(
|
||||
"veh=%d seg=%d lane=%s dir=%s turnaround=%s pos=%.1f speed=%.2f accel=%.2f blocked=%s state=%s standing=%.1f timeToIgnore=%.2f timeUntilAccel=%.2f occ=%s..%s brake=%.1f power=%s traction=%s",
|
||||
v, seg, tostring(edge and edge.index), tostring(dir), tostring(edge and turnaround(edge)),
|
||||
mp.dyn.pathPos.pos, mp.dyn.speed, mp.dyn.accel, tostring(mp.blocked), tostring(mp.state),
|
||||
mp.dyn.timeStanding, mp.dyn.timeToIgnore, mp.dyn.timeUntilAccel,
|
||||
tostring(mp.occupiedBackIndex), tostring(mp.occupiedFrontIndex), lv.dyn.brakingTimer,
|
||||
tostring(info and info.availPower), tostring(info and info.traction))
|
||||
p(line)
|
||||
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)),
|
||||
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 edge ~= nil then
|
||||
local key = seg .. ":" .. edge.index
|
||||
byLane[key] = byLane[key] or {}
|
||||
table.insert(byLane[key], { v = v, pos = mp.dyn.pathPos.pos, speed = mp.dyn.speed, dir = dir, mp = mp })
|
||||
laneSeg[key] = seg
|
||||
table.insert(byLane[key], { v = v, pos = mp.dyn.pathPos.pos, speed = mp.dyn.speed, dir = dir })
|
||||
end
|
||||
bySegment[seg] = bySegment[seg] or {}
|
||||
table.insert(bySegment[seg], string.format("%d(lane=%s,dir=%s,v=%.1f,blk=%s)", v, tostring(edge and edge.index), tostring(dir), mp.dyn.speed, tostring(mp.blocked)))
|
||||
end
|
||||
end
|
||||
for seg, list in pairs(bySegment) do
|
||||
if #list > 1 then
|
||||
p(string.format("SHARED seg=%d: %s", seg, table.concat(list, " ")))
|
||||
end
|
||||
end
|
||||
for _, cars in pairs(byLane) do
|
||||
|
||||
if st.rebuilds >= MAX_REBUILDS 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; the log shows whether that holds
|
||||
-- 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)
|
||||
if i ~= j and ahead and math.abs(b.pos - a.pos) < MAX_GAP then
|
||||
tryIgnore(a, b)
|
||||
local seg = laneSeg[key]
|
||||
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 }
|
||||
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
|
||||
|
||||
local ret : GameScript<table> = {
|
||||
update = function(_userParams : table, _state : GameScriptState<table>, dt : number) : nil
|
||||
local ret : GameScript<ProbeState> = {
|
||||
update = function(_userParams : table, state : GameScriptState<ProbeState>, dt : number) : nil
|
||||
ticks = ticks + 1
|
||||
elapsed = elapsed + dt
|
||||
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)
|
||||
xpcall(dump, function(err : any) p("ERROR " .. tostring(err)) end, dt, state)
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user