chore(prototype): remove GUI position flip; MovePath exchange refused on both threads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
haylanandClaude Bot committed 2026-10-05 23:27:39 +02:00
1 parent 0465a93e04
commit 9623abb868
3 files changed
+11 -108

No files matched your search

+7
View File
@@ -84,3 +84,10 @@ Scanned: the 9 mods in `C:\Users\Public\mod.io\10640\mods\` (e.g. `devers_1`, `m
## 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.
## Position flip via `MovePath` exchange on the GUI thread (run 2026-10-05 ~21:23 UTC)
- On the GUI thread (`guiUpdate`, with a callback), `api.cmd.sendCommand(api.cmd.debug.makeComponentExchangeCmd(follower, leaderMovePath), cb)` fails with the **same** `Error decoding argument #3: The lua object can't be cast to desired type`, and the callback is never reached. Debug mode was active.
- So the error is not about callbacks or threads: **the exchange refuses a `MovePath`**, consistent with its doc comment (`EmissionEmitter` or `ModelInstanceList` only).
- **Conclusion: no script API can write a road vehicle's position or route.** Moving a vehicle past another (jump, flip, teleport) is impossible for line vehicles. The only exception is `jumpToDepotEntity`, which can only target a depot.
- The probe retried every frame (5951 errors) because the error came before the counter was incremented. The experiment is removed, and the probe is logging-only again.
-3
View File
@@ -4,8 +4,5 @@ function data()
updateScript = {
fileName = "overtake_probe.script@update",
},
guiUpdateScript = {
fileName = "overtake_probe.script@guiUpdate",
},
}
end
+4 -105
View File
@@ -1,14 +1,11 @@
-- PROTOTYPE (ticket "Can a script swap the positions of a follower and its leader?"): throwaway,
-- lives only on branch prototype/vehicle-probe.
-- 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
-- PROTOTYPE: throwaway, lives only on branch prototype/vehicle-probe.
-- Every INTERVAL it logs every player-owned road vehicle (segment, lane, position, speed).
-- All experiments (timeToIgnore, jump, set-line, segment rebuild, GUI position flip) 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 elapsed = 0.0
local ticks = 0
@@ -29,14 +26,6 @@ local function turnaround(edge : EdgeId) : integer
return tn.turnaroundEdges[edge.index + 1]
end
local record Car
v : integer
pos : number
pos01 : number
speed : number
dir : boolean
end
local function dump(dt : number)
local player = api.engine.util.getPlayer()
local vehicles = api.engine.getEntitiesWithComponent(api.type.ComponentType.TRANSPORT_VEHICLE, { requireOwnedByPlayer = player })
@@ -56,94 +45,7 @@ local function dump(dt : number)
end
end
-- EXPERIMENT (GUI thread, where sendCommand callbacks work): flip the positions of a follower and its leader by
-- exchanging their MovePath components (route + position) with api.cmd.debug.makeComponentExchangeCmd.
local MAX_FLIPS = 2
local FLIP_SCAN_FRAMES = 60 -- scan for a pair about once per second
local FLIP_TRACE_FRAMES = 600 -- then log both vehicles every 10 frames for ~10 s
local record GuiState
frames : integer
flips : integer
traceLeft : integer
a : integer
b : integer
end
local function movePathOf(v : integer) : Engine.Component.MovePath
return api.engine.getComponent(v, api.type.ComponentType.MOVE_PATH) as Engine.Component.MovePath
end
local function flipLine(v : integer) : string
local mp = movePathOf(v)
if mp == nil then return "veh=" .. v .. " no MovePath" end
local edge, _dir = edgeAt(mp)
return string.format("veh=%d seg=%s lane=%s pos=%.1f v=%.2f", v, tostring(edge and edge.entity), tostring(edge and edge.index), mp.dyn.pathPos.pos, mp.dyn.speed)
end
local function sendFlip(gs : GuiState, follower : integer, leader : integer)
local mpF, mpL = movePathOf(follower), movePathOf(leader)
p("FLIP_TEST before " .. flipLine(follower) .. " | " .. flipLine(leader))
local function done(label : string) : function(any, boolean)
return function(res : any, success : boolean)
p(string.format("FLIP_TEST result %s success=%s res=%s", label, tostring(success), tostring(res)))
end
end
api.cmd.sendCommand(api.cmd.debug.makeComponentExchangeCmd(follower, mpL as Engine.Component), done("follower<-leader"))
api.cmd.sendCommand(api.cmd.debug.makeComponentExchangeCmd(leader, mpF as Engine.Component), done("leader<-follower"))
gs.flips = gs.flips + 1
gs.traceLeft = FLIP_TRACE_FRAMES
gs.a, gs.b = follower, leader
end
-- same pair rule as the engine-side probe: same segment + lane, follower < MAX_GAP behind a moving leader
local function findPair() : integer, integer
local player = api.engine.util.getPlayer()
local byLane : {string : {Car}} = {}
for _, v in ipairs(api.engine.getEntitiesWithComponent(api.type.ComponentType.TRANSPORT_VEHICLE, { requireOwnedByPlayer = player })) do
local lv = api.engine.getComponent(v, api.type.ComponentType.LAND_VEHICLE) as Engine.Component.LandVehicle
local mp = movePathOf(v)
if lv ~= nil and not lv.isTram and mp ~= nil then
local edge, dir = edgeAt(mp)
if edge ~= nil then
local key = edge.entity .. ":" .. edge.index
byLane[key] = byLane[key] or {}
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
for _, cars in pairs(byLane) do
for i = 1, #cars do
for j = 1, #cars do
local a, b = cars[i], cars[j]
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 and b.speed > 1.0 then
return a.v, b.v
end
end
end
end
return nil, nil
end
local function guiStep(gsState : GameScriptState<GuiState>)
local gs = gsState:get()
gs.frames = (gs.frames or 0) + 1
gs.flips = gs.flips or 0
gs.traceLeft = gs.traceLeft or 0
if gs.traceLeft > 0 then
if gs.traceLeft % 10 == 0 then
p(string.format("FLIP_TRACE f=%d %s | %s", FLIP_TRACE_FRAMES - gs.traceLeft, flipLine(gs.a), flipLine(gs.b)))
end
gs.traceLeft = gs.traceLeft - 1
elseif gs.flips < MAX_FLIPS and gs.frames % FLIP_SCAN_FRAMES == 0 then
local follower, leader = findPair()
if follower ~= nil then sendFlip(gs, follower, leader) end
end
gsState:set(gs)
end
local ret : GameScriptWithGui<table, GuiState> = {
local ret : GameScript<table> = {
update = function(_userParams : table, _state : GameScriptState<table>, dt : number) : nil
ticks = ticks + 1
elapsed = elapsed + dt
@@ -152,9 +54,6 @@ local ret : GameScriptWithGui<table, GuiState> = {
elapsed = 0.0
xpcall(dump, function(err : any) p("ERROR " .. tostring(err)) end, dt)
end,
guiUpdate = function(_userParams : table, _state : GameScriptStateReadOnly<table>, guiState : GameScriptState<GuiState>)
xpcall(guiStep, function(err : any) p("ERROR gui " .. tostring(err)) end, guiState)
end,
}
return ret