From 949462566e9015e52c0d2ad0f650a771470d0714 Mon Sep 17 00:00:00 2001 From: ArthurErlich Date: Mon, 5 Oct 2026 22:56:58 +0200 Subject: [PATCH] chore(prototype): rebuild via replaceSegment; record mod/game-folder findings Co-Authored-By: Claude Opus 5.5 --- docs/research/vehicle-probe.md | 11 +++++++++++ mod/content/overtake_probe.script.tl | 21 ++++----------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/docs/research/vehicle-probe.md b/docs/research/vehicle-probe.md index 4319cab..96dbe62 100644 --- a/docs/research/vehicle-probe.md +++ b/docs/research/vehicle-probe.md @@ -52,3 +52,14 @@ Overtaking on two-way roads (one lane per direction) is dropped (decided by the - The script sent 3 `makeWorldBuildProposalCmd` proposals (remove segment + re-add a copy of its `BaseEdge`/`BaseEdgeStreet`, `ignoreErrors = true`) for segments 29701 and 29699 (`country_new_medium`). **None took effect**: both segment ids stayed in use by vehicles until the end of the log, and 29699 was "rebuilt" twice. The game logged no error, and there's no callback in engine state. The copied `BaseEdge.distance` read `0.0`, so the copy is probably incomplete. A next attempt would need `api.engine.util.proposal.makeProposalData` to see the proposal's errors. - The human's manual segment deletion left no trace in the probe data: every vehicle stayed `tvState=1` (en route), with only normal path trimming. - Related find: `api.cmd.makeVehicleSendToDepotCmd(vehicle, sellOnArrival, jumpToDepotEntity)` "teleports" a vehicle to a depot. The engine can relocate vehicles instantly, but only to depots. + +## Lessons from downloaded mods and the game folder (2026-10-05) + +Scanned: the 9 mods in `C:\Users\Public\mod.io\10640\mods\` (e.g. `devers_1`, `move_it_probe_overpass`, `zhenya_auto_assign_terminals`, `celmi_timetables`, `burntnuggie_line_priority_1`), and every `.lua`/`.tl` in the install's `base`, `mods/release` and `dlcs`, including inside the zips. + +- **Callbacks only work on the GUI thread.** Mods send commands from a `guiUpdateScript`/`guiHandleEventScript` (declared in the same `.gs.lua`) and get `function(res, success, resultEntities)` back. Errors are readable via `res.resultProposalData.errorState.messages`. From `update` (engine thread), callbacks fail with `Error decoding argument #3`, which is what we hit. +- **Rebuilding a segment:** `api.engine.util.proposal.replaceSegment(entity, streetTemplate?)` returns a ready `Proposal`. The official campaign (mission 03 `init.script.tl`) sends it from engine state with `makeWorldBuildProposalCmd(proposal, nil, true, true)`. `devers_1` uses it the same way. It keeps lane links, edge objects (signals) and electrification. `api.engine.util.proposal.makeProposalData(proposal)` validates a proposal (`.costs`, `.errorState`) without sending it. +- **Teleport:** only `makeVehicleSendToDepotCmd(vehicle, sellOnArrival, jumpToDepotEntity)`. Mission 05 uses it for a ship, then `makeVehicleSetLineCmd(vehicle, newLine, 0)` and a restart. No coordinate or lane-position teleport exists for line vehicles. `makeCustomVehicleCreateOrUpdateCmd` is aircraft-only ("carrier currently must be Air"). +- `api.engine.util.transport.calcPosition(edge.geometry, t)` gives a world position along an edge geometry. +- `burntnuggie_line_priority_1` sets line **reservation** priority via `makeLineUpdateCmd`. That's path reservation for rail, not road traffic. +- `makeVehicleSetLineCmd` to the vehicle's own line: the lanes planned ahead change, but the engine re-plans them on its own all the time (the leader's changed too, without a command). It doesn't force a lane change, and with `stopIndex=0` it re-targets the line's first stop. diff --git a/mod/content/overtake_probe.script.tl b/mod/content/overtake_probe.script.tl index 63a99bb..183769c 100644 --- a/mod/content/overtake_probe.script.tl +++ b/mod/content/overtake_probe.script.tl @@ -8,7 +8,7 @@ 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_REBUILDS = 3 -- hand-built proposals were silently rejected; now via api.engine.util.proposal.replaceSegment 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 @@ -66,22 +66,9 @@ local function rebuildable(seg : integer) : boolean, string 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)) + -- 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