Compare commits

..
Author SHA1 Message Date
haylanandClaude Bot 1afd1f2e61 chore(prototype): log lane connections at prototype segment nodes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:14:12 +02:00
haylanandClaude Bot 6ed16fb1ab docs(research): third street run; lane connections and the crossing tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:09:40 +02:00
haylanandClaude Bot 3f14583ab4 chore(prototype): passing lanes at 20 m/s to make the normal lane preferred
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:03:10 +02:00
haylanandClaude Bot e807744bf7 chore(prototype): keep street variant B only; record second run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:59:14 +02:00
haylanandClaude Bot 0424e66c6f chore(prototype): passing lanes BUS/TRUCK only; probe logs road template
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:47:05 +02:00
haylanandClaude Bot f7254a7709 chore(prototype): street types with overlaid passing lanes (variants A/B)
Copy of country_new_small with a 0.5 m passing lane per direction at the
centre line, offset +-2.5 into the oncoming lane. A and B use opposite
signs because the unit/sign of LaneConfig.offset is unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:35:17 +02:00
haylanandClaude Bot 9623abb868 chore(prototype): remove GUI position flip; MovePath exchange refused on both threads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:27:39 +02:00
haylanandClaude Bot 0465a93e04 chore(prototype): point probe header at the position-flip ticket
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:25:10 +02:00
haylanandClaude Bot bfe5a22d1d 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>
2026-10-05 23:23:53 +02:00
haylanandClaude Bot 5d3a254e55 chore(prototype): flip follower/leader positions via MovePath exchange on GUI thread
Rebuild switched off for this run. Callbacks log success per exchange.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:22:58 +02:00
haylanandClaude Bot 90829ee9b9 chore(prototype): per-tick trace after rebuild, multilane only; record two-way result
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:14:52 +02:00
haylanandClaude Bot d4fab92fd4 docs(research): segment rebuild splits lanes; component exchange dead in engine state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:01:00 +02:00
haylanandClaude Bot 949462566e chore(prototype): rebuild via replaceSegment; record mod/game-folder findings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:56:58 +02:00
haylanandClaude Bot 39f2c63667 chore(prototype): try jumping follower past leader via MovePath component exchange
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:55:36 +02:00
haylanandClaude Bot e77fabb772 chore(prototype): try re-path via makeVehicleSetLineCmd; record failed rebuild
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:51:26 +02:00
haylanandClaude Bot ea23aa1dc5 chore(prototype): allow rebuild on any lane count to observe vehicle relocation sooner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:45:56 +02:00
haylanandClaude Bot 69b94552c2 fix(prototype): count driving lanes from laneConfigs, require moving leader
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:43:46 +02:00
haylanandClaude Bot 2a08067a6d chore(prototype): dump every 2s for rebuild before/after detail
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:40:42 +02:00
haylanandClaude Bot dcc4b4afae 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>
2026-10-05 22:37:57 +02:00
haylanandClaude Bot 0e76dc453a docs(research): record blocking-bypass search and multilane decision
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:32:32 +02:00
haylanandClaude Bot b53ed477f3 fix(prototype): send component exchange without callback
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:29:28 +02:00
haylanandClaude Bot f9bc0e79fc chore(prototype): try timeToIgnore via debug component exchange
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:26:39 +02:00
haylanandClaude Bot 58fe0d2986 docs(research): record in-game vehicle probe findings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:24:34 +02:00
haylanandClaude Bot b6daeaa328 chore(prototype): log lane layout of shared street segments
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:17:52 +02:00
haylanandClaude Bot aa5dfa655f fix(prototype): read path edges as EdgeIdDir
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:16:17 +02:00
haylan eaf6553cae docs: add mod.io terms research note
Capture the mod.io ToU/AUP clauses that bind a published mod (TpF3 has no
Steam Workshop; mod.io is the official in-game mod browser). Uploads grant
mod.io + the game admin a perpetual irrevocable license; game-side rules
override mod.io's per ToU §1.7. Corrects the map's out-of-scope line.
2026-10-05 22:12:48 +02:00
haylanandClaude Bot c936946eb4 chore(prototype): add in-game vehicle probe game script
Throwaway probe for "Probe vehicle data and native passing in game":
logs every player-owned road vehicle's MovePath/LandVehicle/move-info
state every 5s and groups vehicles by street segment to expose lanes
and oncoming traffic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:06:26 +02:00
7 changed files with 1814 additions and 2 deletions

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
# mod.io Terms: what applies to a published mod
Ad-hoc research 2026-10-05 (no ticket — reference material for the map).
Fetched from the no-chrome legal routes: `https://mod.io/legal/terms?no_links=true` (Terms of Use) and `https://mod.io/legal/aup?no_links=true` (Acceptable Use Policy). `mod.io/terms` itself is a JS app; the same text is also available via `GET /agreements/types/:id/current` (1 = Terms of Use, 3 = Game Terms, 4 = API Access Terms) with an API key — not needed for offline reading.
## Why this matters
TpF3 has **no Steam Workshop**: the official in-game mod browser is mod.io (see `docs/reference/wiki/modding_general_publishing.md`). The map's old "Steam Workshop publishing" out-of-scope line has been corrected to mod.io. This note is the mod.io side of that story; the game-side rules (free, non-commercial, no offensive content, monetization) are already captured in the publishing wiki doc.
## Key clauses (Terms of Use, by number)
- **§1.5** The Acceptable Use Policy is incorporated into the ToU; breaching the AUP is breaching the ToU.
- **§1.7 Priority order** (first wins on conflict): Game Terms > game's AUP > mod.io AUP > ToU clauses 15/16 (EU/AU consumers) > Monetization Terms > ToU. So **Urban Games' own rules override mod.io's**.
- **§1.9** mod.io may change the ToU; 30 days after publication the new terms bind you.
- **§6.2 UGC license** — by uploading a mod you grant mod.io **and the Relevant Game Admin (Urban Games)** a transferable, sublicensable, royalty-free, **irrevocable, non-exclusive, perpetual worldwide** license to use, reproduce, modify, make derivative works of, and distribute the mod "for the sole purpose of providing the Services". Optional *Enhanced License Rights* (sale/resale) only if separately opted in via the UGC Control Panel, and those are revocable.
- **§6.3 warranty** — you warrant the UGC is yours or you have the right to distribute it, and does not violate others' rights. This is what makes the wiki's "no content you don't have copyright or permission to distribute" enforceable.
- **§8 Copyright** — DMCA-style process; mod.io (or the Game Admin) may delete/disable content alleged to infringe.
- **§11 Termination** — mod.io may terminate/suspend the account immediately for material or repeated breach; and the Game Admin may **restrict access to the game's mod services at any time, without notice**.
- **AUP** — standard prohibitions: nothing illegal, no spam/malware, no harassment/defamation, no probing or unauthorised access to the service, no scraping of data you aren't meant to get.
## Implications for this mod
- Free and non-commercial is the game-side rule anyway; nothing new here.
- Uploading = a perpetual irrevocable license to mod.io + Urban Games. Fine for a code-only behavioural mod; just know it before publishing anything with third-party assets (see also the base-game-file reuse rules in the publishing wiki doc).
- The mod can be pulled or access restricted at Urban Games' discretion at any time — no recourse beyond the ToU's notice process.
- Nothing in the ToU/AUP touches the technical side of the mod (script API, staging area, etc.); it only governs distribution and conduct.
File diff suppressed because it is too large. Load diff
+110
View File
@@ -0,0 +1,110 @@
# In-game vehicle probe
Ticket "Probe vehicle data and native passing in game" (part of map "Map: TpF3 overtaking mod"). Run 2026-10-05 on game build 40408, with the throwaway probe `mod/content/overtake_probe.{gs.lua,script.tl}` on branch `prototype/vehicle-probe`. Log excerpt: `probe-logs/2026-10-05-vehicle-probe.txt`.
## How the probe works
- A mod registers a per-tick game script by shipping `content/<name>.gs.lua` (`data()` returns `updateScript = { fileName = "<name>.script@update" }`), the same way the base game's `vehicle/vehicle_modifier.gs.lua` does. No `mod.json` entry is needed. Adding it to an existing save logs `Creating entity for newly added GameScript haylan_tf3_overtake::/overtake_probe.gs`.
- `update` runs every 0.2 s of game time (`dt = 0.2`).
- **Module-level Lua variables don't persist between calls**: the script runs in several or fresh Lua states. Persistent state has to go through `GameScriptState`.
- **No hot reload**: an edited `.tl` takes effect only after the save is reloaded.
- `print` goes to `<Steam>/userdata/<id>/3493540/local/crash_dump/stdout.txt`.
## API facts (observed, overriding tealdef where they differ)
- Player vehicles: `api.engine.getEntitiesWithComponent(TRANSPORT_VEHICLE, { requireOwnedByPlayer = api.engine.util.getPlayer() })`. Road vehicles also carry `LAND_VEHICLE` and `MOVE_PATH`.
- `MovePath.path.edges` is a **1-based Lua table of `EdgeIdDir` userdata** (`.edgeId`, `.dir`), **not** `{EdgeId, boolean}` as the tealdef says. The current edge is `edges[dyn.pathPos.edgeIndex + 1]`.
- `EdgeId.entity` is the **street segment** (it carries `BASE_EDGE` and `TRANSPORT_NETWORK`). `EdgeId.index` is the **lane** within that segment's `TransportNetwork.edges`.
- `TransportNetwork.turnaroundEdges[index + 1]` is the opposite lane. `TransportNetwork.edges[i]` has `transportModes`, `speedLimit` and `curSpeed`.
- `BaseEdge.laneConfigs` lists the lanes, with `forward`, `offset`, `width` and `transportModes`. Mode keys print as integers: sidewalks `[0,1]`, driving lanes `[2,3,4]`.
- `landVehicleMoveSystem.getVehicleInfo` only returns `availPower`, `traction`, `tractiveEffort` and `vehicleAvailPower`.
- `MovePath.blocked` stayed `0` for every vehicle the whole time, including vehicles clearly queued behind a slower one. It doesn't signal "held up by the vehicle ahead".
- `MovePath.dyn.timeToIgnore` is negative and counts down for almost every vehicle. It was briefly positive (+9.4) once, right after a stop. Its meaning is unknown.
## Observed traffic behaviour
| Road | Lanes per direction | What happened |
|---|---|---|
| `country_new_medium` (segments 6527 → 29697) | 2 (lanes 3 and 4 forward) | 29251 (power 243) followed 29852 (power 160) in lane 4 over 6 segments, 24–30 m behind. It accelerated to 17.7 m/s and then dropped to 15.1 m/s to match the vehicle ahead. Lane 3 was free and stayed unused. |
| `country_new_medium` (segment 28894) | 2 | The two vehicles were in different lanes (3 and 4), but their order never changed. This looks like lane choice for the next turn, not passing. |
| `country_new_small` (segments 26513 → 28566) | 1 (lane 2 forward, lane 1 opposite) | 29251 was held at exactly 16.67 m/s (29851's top speed) behind 29851, below the road's 22.2 m/s limit, for 6+ segments. |
## Verdict
- The engine does **not** pass natively, on either multilane or two-way roads. Bus lanes weren't tested.
- The read side is sufficient: a script can tell, per vehicle, which segment, lane and direction it's in, where it is, how fast it's going, and what the opposite lane is. So both the "held back" condition and an oncoming-traffic check can be computed. "Held back" must be derived (same segment and lane, small gap, speed below the vehicle's capability), because `blocked` doesn't report it.
- **No documented command can make a vehicle ignore the one ahead** (see `overtake-feasibility.md`).
## Searching for a way to make a vehicle ignore the one ahead
- **Script commands:** none of the commands in `api.cmd` moves a vehicle, changes its lane, or turns off the queueing. The full list was checked.
- **Model metadata:** `landVehicle` (engines, topSpeed, weights) and `extent` have no following or blocking parameters.
- **`Transformator.LandVehicleScriptingApi` / `TransfOutput.setUserTransf`:** a per-model animation script. It can offset the model on screen, which could make a vehicle look like it changes lanes, but it doesn't touch the simulation.
- **Executable strings:** `LandVehicleMoveSystem.cpp` contains `GetNextEdgeTrafficPenaltyAndAlternatives`, `existingPathLaneSwitch` and `switchIndices`. So the engine itself switches lanes at segment boundaries based on a traffic penalty, which matches the lane split seen on segment 28894. None of it is exposed to scripts.
- **`api.cmd.debug.makeComponentExchangeCmd(entity, MovePath)`** (save loaded with "Debug mode is active"): building the command with a `MovePath` **succeeds**. `api.cmd.sendCommand(cmd, callback)` fails with `Error decoding argument #3`, because engine-state scripts can't pass a callback (base scripts never do). The version without a callback was committed but **never ran in game**. Whether the swap is applied, and what a positive `timeToIgnore` does, are both **untested**.
## Decision
Overtaking on two-way roads (one lane per direction) is dropped (decided by the user, 2026-10-05). The effort continues on multilane roads only: a held-back vehicle should switch to a lane the slower vehicle isn't in.
## Segment rebuild (ticket "Can rebuilding a road segment move a passer past its blocker?")
- 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.
## Segment rebuild via `replaceSegment` (run 2026-10-05 ~20:55 UTC)
- `api.engine.util.proposal.replaceSegment(seg)` plus `makeWorldBuildProposalCmd(p, nil, true, true)` from engine state **works**: segments 29702, 21304 and 29012 never appeared again after their rebuild (they got new ids).
- **After all 3 rebuilds, the follower and leader ended up in different lanes** (`country_new_medium`, 2 lanes per direction). Each time the *leader* moved from lane 4 to lane 3, either right away or one segment later, and the follower stayed in lane 4.
- #1 (seg 29702): follower 29251 at 50.4 m behind leader 29852 at 76.7 m. Afterwards, on seg 29699, 29251 is at 29.1 m (lane 4) and 29852 at 20.2 m (lane 3). 29251 drives 18.6–19.6 m/s, above its earlier 16.67, and stays a segment ahead. **The follower got past.**
- #2 (seg 21304): 29851 (leader) moved to lane 3 on seg 27833. The lanes split, but the order was unchanged.
- #3 (seg 29012): 29251 (leader) moved to lane 3 on seg 29322.
- Without rebuilds, the same pair stayed together in lane 4 for 6 segments (first probe). The engine also switches lanes by itself at segment ends sometimes, so 3/3 is suggestive, not proof.
- Likely cause (human's hypothesis): the vehicles' `MovePath` refers to lanes of the removed segment, so the engine must re-attach them to the new segment and re-plan, and a vehicle can land in a different lane.
- **Debug `MovePath` exchange is unusable from engine state**: `api.cmd.sendCommand(api.cmd.debug.makeComponentExchangeCmd(v, movePath))` fails with `Error decoding argument #3` even **without** a callback. So the earlier error wasn't caused by the callback. The only untried route is the GUI thread.
- Not yet tested: a rebuild on a two-way (one lane per direction) road.
## Rebuild on a two-way road (run 2026-10-05 ~21:12 UTC)
- 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.
## 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.
## Overlaid passing lanes (ticket "Can a street type with overlaid passing lanes fake overtaking on single-lane roads?")
- Prototype: `mod/content/streets/overtake_proto_{a,b}.street_template.lua`, copies of `country_new_small` with a 0.5 m passing lane per direction at the centre line, offset ±2.5 into the oncoming lane (A and B use opposite signs). A mod street template sits anywhere under `content/` and refers to base resources with `::/…`.
- First in-game run (human, 2026-10-05): the template loads, the lanes are visibly offset as intended, and vehicles can drive on the passing lanes. **Town (non-player) cars sometimes choose the passing lane.** **A faster bus behind a slower vehicle does not switch to the empty lane**: the switching logic is missing.
- Changes for the next run: passing lanes allow only `BUS`/`TRUCK` (keeps private `CAR` town traffic out), and the probe logs the road template per vehicle (`road=`), since lane indices 2/3 also mean ordinary lanes on `country_new_medium`.
- Hypothesis for the switch: the engine only re-chooses lanes at segment boundaries (`GetNextEdgeTrafficPenaltyAndAlternatives`), so short segments give a blocked bus more chances to pick the passing lane. Fallback trigger: `replaceSegment` ahead of the pair (made the leader re-plan lanes on multilane roads).
- Second run (2026-10-05 ~21:50 UTC, variant B, passing lanes BUS/TRUCK only). The human judged **variant B correct** (backward passing lane offset -2.5, forward +2.5), so variant A was removed. On the prototype road, lane 4 is the normal forward lane and lane 3 the forward passing lane.
- **An overtake happened**: fast 29251 (lane 4, 22.22 m/s) got past slow 29851 over segments 29707 → 30211 (side by side) → 30215 → 29708 → 29042 and stayed ahead. But the *slow* vehicle was in the passing lane (lane 3), and the passer went past in the normal lane.
- The engine treats both lanes as equal: some vehicles cruise in the passing lane almost the whole way (29796: 28/28 samples in lane 3, 27775: 29/29). Others mix (29251: 33 in lane 4, 14 in lane 3).
- Vehicles slowed to 9–10 m/s and briefly stopped on segment 27290 (presumably the end of the prototype stretch): a merge from two lanes into one.
- Visually the lane change is abrupt (human: "the lanes need a smoother transition"): the switch happens at the node, which has almost zero length on a straight, and the jump is about 5–6 m sideways.
- Next ideas: a lower speed limit on the passing lane (prefer the normal lane unless it's blocked), a smaller offset or transition segments for a smoother change.
- Third run setup: passing lanes at 20 m/s (normal lanes 22.22 m/s), everything else unchanged. Question: does a lower limit keep vehicles in the normal lane and send them to the passing lane only when the normal lane is blocked?
- Third run (20 m/s passing lanes): **no change.** Slow buses still sat in the left passing lane (29849 and 29851: 29/29 samples in lane 3); fast 29251 split 8/11. The engine doesn't pick lanes by speed limit (the slow buses top out at 16.67 < 20 anyway). Observed pattern: a vehicle gets a lane when it enters the stretch and keeps it; it doesn't move over when blocked; passes happen only when fast and slow happen to be in different lanes.
- Lane connections are editable: `BaseNodeConfig.laneConnections` (`LaneConnection{segment0, lane0, segment1, lane1, withRoad, withTram}`, lanes zero-based left-to-right seen from the node) plus `userModifiedLaneConnections`, scriptable via `SimpleStreetProposal.nodeConfigsToAdd`. The game also has a native **crossing tool** ("allows to change the road and tram lanes at crossings", `UI::LaneModifier` in the executable). Next test (option a, by hand): at the stretch's entry junction, connect the normal road only to the right lane, to see whether entry control works and whether a blocked fast bus moves left inside the stretch.
- Fourth run (after the human's crossing-tool edits): **entry control not effective.** Slow buses 29849, 29851 and 29852 still entered prototype stretches (segments 21304, 29012) in the left lane 3 and stayed there (12–14/12–14 samples). 29850, 29848 and the fast 29251 entered in lane 4. No vehicle switched lanes inside a stretch. It's unknown whether or where the edits were saved, so the probe now logs the lane connections (`NODE … userModified= conns=`) at both nodes of every occupied prototype segment.
+4 -2
View File
@@ -9,7 +9,9 @@
"summary": "Vehicle overtaking (WIP)",
"name": "Overtake",
"tags": [
"script"
"Script Mod",
"Car",
"Road"
],
"url": ""
"url": "https://git.arthurerlich.de/haylan/tpf3-overtake"
}
+8
View File
@@ -0,0 +1,8 @@
-- PROTOTYPE (ticket "Probe vehicle data and native passing in game"): throwaway, lives only on branch prototype/vehicle-probe.
function data()
return {
updateScript = {
fileName = "overtake_probe.script@update",
},
}
end
+89
View File
@@ -0,0 +1,89 @@
-- 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 elapsed = 0.0
local ticks = 0
local function p(s : string)
print(TAG .. s)
end
local function edgeAt(mp : Engine.Component.MovePath) : EdgeId, boolean
-- path.edges is 1-based, elements are EdgeIdDir userdata (tealdef says {EdgeId, boolean})
local e = (mp.path.edges as {any})[mp.dyn.pathPos.edgeIndex + 1] as UtilPathfinding.EdgeIdDir
if e == nil then return nil, nil end
return e.edgeId, e.dir
end
local function turnaround(edge : EdgeId) : integer
local tn = api.engine.getComponent(edge.entity, api.type.ComponentType.TRANSPORT_NETWORK) as Engine.Component.TransportNetwork
if tn == nil or tn.turnaroundEdges == nil then return nil end
return tn.turnaroundEdges[edge.index + 1]
end
-- short street template name of a segment, so passing-lane use on the prototype road can be told apart
local function roadOf(seg : integer) : string
local be = api.engine.getComponent(seg, api.type.ComponentType.BASE_EDGE) as Engine.Component.BaseEdge
if be == nil then return "?" end
return (tostring(be.roadTemplate):match("([^/]+)%.street_template$")) or tostring(be.roadTemplate)
end
-- lane connections at both nodes of a segment, to check whether crossing-tool edits exist where vehicles enter
local function nodeLog(seg : integer)
local be = api.engine.getComponent(seg, api.type.ComponentType.BASE_EDGE) as Engine.Component.BaseEdge
if be == nil then return end
for _, node in ipairs({ be.node0, be.node1 }) do
local cfg = api.engine.getComponent(node, api.type.ComponentType.BASE_NODE_CONFIG) as Engine.Component.BaseNodeConfig
if cfg == nil then
p(string.format("NODE seg=%d node=%d no BaseNodeConfig (default connections)", seg, node))
else
local conns : {string} = {}
for _, lc in ipairs(cfg.laneConnections) do
if lc.segment0 == seg or lc.segment1 == seg then
table.insert(conns, string.format("%d:%d->%d:%d", lc.segment0, lc.lane0, lc.segment1, lc.lane1))
end
end
p(string.format("NODE seg=%d node=%d userModified=%s conns=%s", seg, node, tostring(cfg.userModifiedLaneConnections), table.concat(conns, " ")))
end
end
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", ticks, tostring(dt), #vehicles))
local protoSegs : {integer : boolean} = {}
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)
p(string.format(
"veh=%d road=%s 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 roadOf(edge.entity) or "-", 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 edge ~= nil and roadOf(edge.entity) == "overtake_proto_b" then protoSegs[edge.entity] = true end
end
end
for seg, _ in pairs(protoSegs) do nodeLog(seg) end
end
local ret : GameScript<table> = {
update = function(_userParams : table, _state : GameScriptState<table>, 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)
end,
}
return ret
@@ -0,0 +1,43 @@
-- PROTOTYPE (ticket "Can a street type with overlaid passing lanes fake overtaking on single-lane roads?"):
-- throwaway, lives only on branch prototype/vehicle-probe.
-- Looks like country_new_small (same style, one visible lane per direction), but each direction gets a narrow
-- extra passing lane at the centre line, pushed by `offset` over the oncoming lane. Passing lanes run at 20 m/s
-- (normal lanes 22.22) to test whether a lower limit keeps vehicles in the normal lane unless it's blocked.
-- Offsets B -2.5 / F +2.5 put the passing lanes over the oncoming lanes (variant B; the opposite signs, variant A,
-- were wrong and are removed).
function data()
return {
availability = { yearFrom = 0, yearTo = 0 },
cost = 180,
country = true,
defaultWithCrosswalk = false,
description = {
description = "PROTOTYPE overtaking road, variant B: country small road with overlaid passing lanes (offset B -2.5 / F +2.5).",
icon = "::/infrastructure/street/country/country_new_small.tga",
name = "Overtake proto B (passing lanes B -2.5 / F +2.5)",
previewIcon = "::/infrastructure/street/country/country_new_small_preview.tga",
},
emissions = { noise = 0, pollution = 0, pollutionRadius = 0, radius = 0 },
fillGroundTex = "::/infrastructure/street/shared/street_fill.gtex",
laneConfigs = {
{ forward = false, height = 0, offset = -1, speed = 22.22, transportModes = { "PERSON", "CARGO" }, width = 2 },
{ forward = false, height = 0, offset = -0.5, speed = 22.22, transportModes = { "CAR", "BUS", "TRUCK" }, width = 5 },
-- backward passing lane, meant to sit over the forward driving lane (BUS/TRUCK only: keeps town cars out)
{ forward = false, height = 0, offset = -2.5, speed = 20, transportModes = { "BUS", "TRUCK" }, width = 0.5 },
-- forward passing lane, meant to sit over the backward (oncoming) driving lane (BUS/TRUCK only)
{ forward = true, height = 0, offset = 2.5, speed = 20, transportModes = { "BUS", "TRUCK" }, width = 0.5 },
{ forward = true, height = 0, offset = 0.5, speed = 22.22, transportModes = { "CAR", "BUS", "TRUCK" }, width = 5 },
{ forward = true, height = 0, offset = 1, speed = 22.22, transportModes = { "PERSON", "CARGO" }, width = 2 },
},
maintenanceCost = 30,
menuCategory = { categories = { { category = "roads_country", filterCategories = { }, order = 442 } } },
modifiers = { maxSpeedModifier = 0, noiseModifier = 0, pollutionModifier = 0 },
pedestrianWalkPenalty = true,
priority = 200,
roadType = "STREET",
sidewalkFillGroundTex = "::/infrastructure/shared/none.gtex",
simBuildable = false, -- towns must not build the prototype
streetStyle = "::/infrastructure/street/country/country_new_small.street",
builderAudioRes = "::/gui/construction/sound/buildoze_street.builder_audio",
}
end