Actor::K2_GetActorLocation
confirmed
World-space location of the actor.
- Returns
FVector Location World location in Unreal units (cm).
Every Train Sim World 6 function and class the TSM client talks to through UE4SS reflection — 72 of them, across 12 headers. This page is generated from the doc comments in Client/TSM/Natives/*.hpp, so it is never out of date with the mod.
Native reflection signatures for Unreal's AActor / UActorComponent. See README.md for the doc-comment convention and why fields are void*. No UE4SS/Unreal includes on purpose: every *_Params here matches the Unreal ProcessEvent parameter block by raw layout, so this header parses standalone.
Actor::K2_GetActorLocationWorld-space location of the actor.
FVector Location World location in Unreal units (cm).Actor::K2_GetActorRotationWorld-space rotation of the actor.
FRotator Rotation Pitch/Yaw/Roll in degrees (memory order).ActorComponent::GetOwnerThe actor that owns this component (for reading a component's world transform via the actor's K2_GetActor* functions).
AActor* ReturnValue Owning actor, or null.Actor::GetAttachParentActorThe actor this actor is attached to. Confirmed in-game: the driver pawn (TS2DefaultDriverPawn_C) is attached to the rail vehicle it is driving (e.g. RVV_..._Class805_..._C), so this is TSM's link from the local player to their driven vehicle.
AActor* ReturnValue Parent actor, or null when unattached.UDLCManagementFunctionLibrary — TSW's content-on-demand API (module
TS2Prototype). A loco's cooked assets aren't loadable on a client that hasn't
loaded that loco this session (the AssetRegistry only indexes primary assets
like RVDs, and leaf meshes aren't reachable until the content is loaded). The
WORKING recipe for rendering a remote player's train (see Docs/dynamic-asset-
loading.md, and Game/Assets.cpp pull_remote_mesh):
1. GetAllVehiclesForFreeRoam → find the entry whose SoftObject is under the
loco's mount (e.g. "/AABS_Class805/").
2. RequestContentLoad(that entry, FreeRoamTrainEntry) — makes the loco's
content reachable (async).
3. KismetSystemLibrary::LoadAsset(mesh path) — pulls the specific mesh in.
4. Find the mesh by object name (leaf meshes are never registry-indexed).
Enum values are from the TSW6 dump (Docs/Dumps/CXXHeaderDump/TS2Prototype_enums.hpp).
DLCManagementFunctionLibrary::GetAllVehiclesForFreeRoamEnumerate every free-roam-spawnable vehicle the player owns — INCLUDING DLC whose pak isn't mounted yet (this is what the sandbox/free-roam menu lists). OutList is TArray<FCachedTrainEntry>; each entry's FCachedContentPiece::SoftObject (offset 0) is the train-entry soft path (an RVD) to feed the content loader.
enumerates owned DLC incl. not-yet-loaded locos.
DLCManager.hpp:52DLCManagementFunctionLibrary::RequestContentLoadAsync (void) content load. Feed a free-roam entry's SoftObject with category FreeRoamTrainEntry. Doesn't crash (unlike the Synchronous variant on loco targets) and makes the loco's content *reachable* over ~seconds; a streamable LoadAsset then pulls the actual meshes.
the content trigger used by Assets::request_loco_content.
DLCManager.hpp:61DLCManagementFunctionLibrary::IsAssetPathLoadableWhether TSW's content system can load the asset at `InPath`. NOTE: returns TRUE for owned-but-unloaded leaf meshes even though they aren't yet directly loadable — it means "TSW knows about it", not "reachable right now". Diagnostic — no side effects.
(diagnostic)
DLCManager.hpp:86DLCManagementFunctionLibrary::RequestContentLoadSynchronousSynchronous content load. DEAD END: on real loco targets (RVD / formation) it hits a UE fatal check() and crashes the game (minidump-confirmed); on a mesh target it no-ops. Use the async RequestContentLoad instead. Kept only to document the failure.
do not call.
DLCManager.hpp:100DLCManagementFunctionLibrary::GetActiveMountPointsReturns TSet<FString> of TSW's registered content mount points (e.g. "/AABS_Class805/"). NOTE: presence here means TSW knows the content, NOT that its files are reachable by the loader yet.
(diagnostic)
DLCManager.hpp:108Vehicle feature surface for SOS phase E (doors/pantograph -> the wire's VehicleFeatures bits). Mined from the dump 2026-07-18; door state is read via plain reflected PROPERTIES (no calls — the sandbox spike taught us cold UFunction calls into unfamiliar systems can fatal-check), pantograph via IsDeployed(), the same call class as the proven-safe component getters. Enum values (TS2Prototype_enums.hpp): EDoorOpenState (UDoorComponent.State): 0 Open, 1 Closed, 2 Opening, 3 Closing EDoorState (UDoorV2Component.TargetState): 0 Indeterminate, 1 Open, 2 Closed Lights: URailVehicleComponent only exposes switch DELEGATES (no state property found) — the wire's lights byte stays reserved until a readable source turns up.
UDoorComponent (property reads only)Legacy door component; `State` (EDoorOpenState, u8) is the live open state, `Position` (float 0..1) the blend.
(dump 2026-07-18) — verify owners via `train features`
Doors.hpp:19UDoorV2Component (property reads only)VHID door; `TargetState` (EDoorState, u8) is where the door is headed (Open while opening — right level-state for sync).
(dump 2026-07-18)
Doors.hpp:27UPantographComponent::IsDeployedWhether this pantograph is up.
bool ReturnValue(dump 2026-07-18)
Doors.hpp:35UTS2GameplayFunctionLibrary — TSW's gameplay BP library (module TS2Prototype). Currently catalogues the scenario/world-entry path: the autonomous test harness needs the game IN a route world (a pawn in the menu background is not enough — no ribbon network exists there), and LoadScenario is the menu's own function for entering one. See Game/Assets.cpp `asset scenario-load`.
TS2GameplayFunctionLibrary::LoadScenario(const UObject* WorldContextObject, UScenarioDefinition* Scenario) — the route/scenario select screen's own "enter this scenario" call. Triggers the full level travel + scenario setup (delegates TS2GameInstance::OnLoadScenarioBegin/Complete fire around it). WorldContextObject: the menu pawn works (same convention as the DLC library calls in Game/Assets.cpp).
derived from the TSW6 dump only; verify in-game.
Gameplay.hpp:16UUIFunctionLibrary::SetSelectedRoute / SetEnteredThroughRouteSelectionThe main-menu's selection state helpers. DO NOT call these with hand-marshalled structs: a copied 0xF0 FCachedRouteDefinition is unverified state, and GetFullCachedContentList (the natural source) HANGS THE GAME THREAD from the menu (2026-08-28, twice). The world-entry gate lives inside LoadScenario's own state machine instead — see Game/Assets.cpp list_scenarios.
wedged the game thread; kept only as a warning.
Gameplay.hpp:26KismetSystemLibrary::ExecuteConsoleCommandRun a command string through the engine's console dispatcher. Unlike calling a UFunction directly, this reaches BOTH UFUNCTION(exec) commands and native IConsoleManager-registered commands — e.g. TSW's ts2.rtt.*/ts2.dbg.* family (SpawnFormation, RouteMeTo, RemoveTrainFromTrack, ...), which don't appear in the reflection dump at all because they're not UFunctions. Candidate alternative to hand-marshalled SpawnFormation/SetTrainEntry calls — see tsm-console-command-spawn-lead memory. Console output goes to UE's own log, not TSM's — observe via before/after diffs or in-game behaviour, not a captured return string.
WorldContextObject const UObject* Any in-world object.Command FString e.g. "ts2.rtt.SpawnFormation ...".SpecificPlayer APlayerController* Target player; null = default.KismetSystemLibrary::DrawDebugSphereDraw a wireframe debug sphere (used for remote player markers).
WorldContextObject const UObject* Any in-world object.Center FVector World location (cm).Radius float Sphere radius (cm).Segments int32 Wireframe segments.LineColor FLinearColor RGBA 0..1.Duration float Seconds to persist (0 = one frame).Thickness float Line thickness.KismetSystemLibrary::DrawDebugBoxDraw a wireframe debug box (used for remote train markers).
WorldContextObject const UObject* Any in-world object.Center FVector World location (cm).Extent FVector Half-size in each axis (cm).LineColor FLinearColor RGBA 0..1.Rotation FRotator Pitch/Yaw/Roll (deg).Duration float Seconds to persist (0 = one frame).Thickness float Line thickness.RailVehicle / RailVehicleDefinition — the higher-level rail-vehicle objects. From the spike: RailVehicle has comparatively few (and less relevant) functions, RailVehicleDefinition holds top-level properties (destination, driver, etc.). The concrete link between these and RailVehicleComponent was not established in Live View — record findings here as they are confirmed. Cataloged for the docs; not wired into TSM's train sync yet.
RailVehicle::OnSpawnCalled when a rail vehicle is spawned into the world; a hook point for reacting to (or driving) TrainSpawner results.
RailVehicle.hpp:15RailVehicle::SeatCharacterMap(Property) map of seats -> occupying characters. Candidate for resolving which vehicle the local player is driving/seated in.
RailVehicle.hpp:21RailVehicle::UsesSimpleControlsDoes this loco ship as a SIMPLE-CONTROLS unit (drive it with a throttle and nothing else) or a full-startup one (battery, pantograph, VCB, key, reverser... before it will take power)? Reads the loco's `URailVehicleDefinition::bUsesSimpleControls` (+0x028C) — a CONTENT DESCRIPTOR, not a runtime mode, so there is no toggling it on a spawned consist.
bool ReturnValueread by `train move simple audit`. Class 805 answers FALSE on all five cars (2026-08-07) — which is what ended the attempt to drive puppets from the cab. See Natives/VirtualHID.hpp.
RailVehicle.hpp:27RailVehicleComponent — the component that carries the rail-vehicle behaviour (velocity, consist links). This is the object TSM reads to sync a player-driven train. TSW6 architecture (confirmed in-game 2026-07-13): each rail vehicle is a pair — a visual actor `RVV_<...>_C` (what the driver pawn is attached to) and a model/physics actor `RVM_<...>_C`. The RailVehicleComponent (instance name `RailVehiclePhysicsComponent0`) is owned by the RVM actor, not the RVV the pawn attaches to. TSM bridges RVV -> RVM via the actor's GetOwner/attach chain (see Game/Train.cpp `rail_component_for`).
RailVehicleComponent::GetForwardsVelocitySigned forward speed of this vehicle along the track.
float speed_ms Metres per second; positive = travelling forward.(verified in-game 2026-07-13: returns m/s)
RailVehicleComponent.hpp:18RailVehicleComponent::GetVelocityFull 3D velocity vector of this vehicle.
FVector ReturnValue Velocity (cm/s) in world space.RailVehicleComponent::GetAngularVelocityAngular velocity of this vehicle.
FVector ReturnValue Angular velocity (deg/s or rad/s — verify).RailVehicleComponent::GetPositionDO NOT USE. Whatever this returns, it is not a readable world position at the return-property offset.
FVector ReturnValue Garbage.calibrated against a normally-driven train 2026-08-05 and FAILED. The player drove a spawned consist 600 m under its own power; GetPosition reported (-0,-14,-3), i.e. the world origin. Other reads on the same session returned 7e29 and 2.4e11. Its y component was -14 on three unrelated consists while x/z varied wildly, so the return is not a plain FVector here. This native produced a confident WRONG root-cause diagnosis of the puppet 1 km cull (see the tsm-puppet-sim-anchor memory) — do not build another measurement on it without calibrating first.
RailVehicleComponent.hpp:48RailVehicleComponent::GetNextRailVehicleThe next vehicle in the consist from this one (walk to enumerate the formation one link at a time).
RailVehicleComponent* ReturnValue Next vehicle, or null at the end.(in-game 2026-07-13 it returned null on the driven car — either not the right getter or needs a different start; the full-consist API is still to be identified on the component)
RailVehicleComponent.hpp:67RailVehicleComponent::GetFormationThe whole consist this vehicle belongs to (loco + wagons), in order — every car regardless of which cab you're in. This is TSM's consist enumerator (GetNextRailVehicle yields nothing from an end cab). Read via Game/Train.cpp `call_object_array`, which reads the TArray and DestroyValue()s it so it doesn't leak.
TArray<RailVehicleComponent*> ReturnValue All vehicles in order.(present on the component, verified in-game 2026-07-13)
RailVehicleComponent.hpp:80URailVehicleComponent::SetOverrideLocationKinematically place this vehicle at a world location (candidate M2 teleport primitive). Semantics unconfirmed — may be the world-origin-shift helper, not a teleport; the T3 probe decides.
FVector Location World position (cm).(dump 2026-07-21)
RailVehicleComponent.hpp:101URailVehicleComponent::SetOverrideSpeedfloat Speed m/s (0 = hold in place).(dump 2026-07-21)
RailVehicleComponent.hpp:110URailVehicleComponent::SetSimpleEffortOrBrakeCoefficientSimple-controls drive input: the game's own physics drives the vehicle (candidate M4a). +effort accelerates; bStop brakes/holds.
float EffortOrBrake [-1..1] throttle/brake coefficient.bool bStop(dump 2026-07-21)
RailVehicleComponent.hpp:116URailVehicleComponent::IsDerailedbool ReturnValue The T3 success check after a move.(dump 2026-07-21)
RailVehicleComponent.hpp:125URailVehicleComponent::GetFormationLengthfloat ReturnValue Whole-consist length in **METRES**, not cm.2026-08-05 — a 5-car Class 805 returns 129, and its measured geometry is 104 m centre-to-centre plus one 26 m car = 130 m end to end. Reading it as cm made `train move walk` space the cars 25.8 CM apart and stack the whole consist inside itself. Multiply by 100 before mixing it with any other TSM length.
RailVehicleComponent.hpp:131URailVehicleComponent::GetForwardVectorFVector ReturnValue Unit forward direction in world space.(dump 2026-07-21)
RailVehicleComponent.hpp:141URailVehicleComponent::ApplySimulationPresetBring the vehicle's SYSTEMS to a preset state — the game's own "prepare this train". Looked like the answer to the master key, which no amount of lever dragging will turn.
ESimulationPreset Preset 0 Parked, 1 RunningStopped, 2 RunningActiveFName ConfigName NAME_None worksNO-OP 2026-08-07 — preset 2 moves LastAppliedPreset to RunningActive on every car, but the master key stays out and bStop stays 1, so the consist is no more drivable than before.
RailVehicleComponent.hpp:147URailVehicleComponent::ApplyVHIDPresetDrive a NAMED SET of controls to a target over time — the driver's assistant / AI path for operating a cab. Needs a GameplayTasks component and a preset name from the vehicle's VHIDPresets array.
UGameplayTasksComponent* GameplayTasksComponentRefAController* ControllerRefFName PresetName, float TargetInputValue, float ErrorTolerance, float MinMoveTime, float MaxMoveTime, float RateOfChange2026-08-07 — the Class 805's 11 VHIDPresets are ALL lighting. No power, key or startup preset is shipped, so there is nothing here to drive a train with (`train move simple audit` dumps them).
RailVehicleComponent.hpp:158URailVehicleComponent::ResetPhysicsRe-seat the sim. RULED OUT as the puppet re-anchor primitive.
ESimulationPreset PresetFName ConfigurationNameOUT 2026-08-05 — presets 0 and 1 changed nothing observable on a hard-set consist; **preset 2 CRASHED THE GAME**. Do not call it with a preset above 1. Preset 3 remains untested and is not worth another crash to find out.
RailVehicleComponent.hpp:171TSW6's sandbox (free-roam) spawn path, mined from the dump 2026-07-17 for SOS
phase C. The chain TSM drives (and hooks) to spawn real consists at runtime:
UTimetableRuntimeExtensionLibrary (BP function library, module TS2Prototype):
FindSpawnableFormations(Search) -> TArray<UTrainEntryDefinition*>
FindTrainSpawnLocationNearDriverPawn(pawn, dist) -> FNetworkRibbonLocation + dir
SpawnFormation(ctx, entry, ribbonLoc, dir) -> void (no handle!)
UNetworkBlueprintFunctionLibrary:
GetTransformAtTrackLocation(ctx, loc, out FTransform) — ribbon -> world.
ASandboxManager — the free-roam menu's state machine (Idle/SpawningTrains/
RemovingTrains/...); StartRemovingTrain(RVComponent) is the removal probe.
FNetworkRibbonLocation (0x1C bytes: FGuid RibbonReference @0x08, float
RibbonLocation @0x18) is treated as an opaque blob — copied whole between out
params and call frames, never interpreted. EDirectionOfTravel: 0 = Forwards,
1 = Backwards (TS2Prototype_enums.hpp).
SpawnFormation returns nothing, so the spawned consist is found by a
before/after diff on live RailVehicleComponents (Game/Spawner.cpp).
UTimetableRuntimeExtensionLibrary::FindSpawnableFormationsSearch the loaded spawnable formations (free-roam entries). Only returns entries whose content is loaded this session.
TArray<FString> Search Substring filters (empty = all).TArray<UTrainEntryDefinition*> ReturnValue(dump 2026-07-17)
Sandbox.hpp:32UTimetableRuntimeExtensionLibrary::FindTrainSpawnLocationNearDriverPawnA valid on-track spawn location near the driver pawn.
ADriverPawn* DriverPawnfloat DistanceCMFNetworkRibbonLocation& OutRibbonLoc (out, 0x1C)EDirectionOfTravel& OutLocalSpawnDir (out, u8)bool ReturnValue False = no valid location found.(dump 2026-07-17)
Sandbox.hpp:40UTimetableRuntimeExtensionLibrary::SpawnFormationSpawn a formation (consist) at a track location. Returns void — resolve the new consist via component diff. TSM also hooks this to catch the game's own free-roam menu spawns.
UObject* WorldContextObjectUTrainEntryDefinition* TrainEntryFNetworkRibbonLocation RibbonLoc (0x1C)EDirectionOfTravel LocalSpawnDir (u8)(dump 2026-07-17)
Sandbox.hpp:51StartSettingPathForRVURailVehicleComponent* RailVehicleComponent(dump 2026-08-05)
Sandbox.hpp:84AddWaypointsForRVPathURailVehicleComponent* RailVehicleFNetworkRibbonLocation SelectedRibbonLocationsame param pair as SetDestinationForRV; a path may need waypoints before a destination will take.
Sandbox.hpp:87RemoveCurrentPathForRVURailVehicleComponent* RailVehicleComponentthe "undo" if a path gets stuck.
Sandbox.hpp:92SetSettingPathStateboolGetStateESandboxManagerState (u8): 0 Idle, 1 SpawningTrains, 2 RemovingTrains, 3 SettingPath, 4 RemovingPath, 5 AddingWaypointToPath.UNetworkBlueprintFunctionLibrary::GetTransformAtTrackLocationWorld transform of a ribbon location. FTransform (UE4.26 x64): FQuat @0x00, FVector Translation @0x10, Scale @0x20 — TSM reads the translation only.
bool ReturnValue False = invalid location.(dump 2026-07-17)
Sandbox.hpp:124UNetworkBlueprintFunctionLibrary::IsTrackLocationValidUNetworkBlueprintFunctionLibrary::AdvanceTrackLocationWalk along the ribbon network from a location by a distance, following ribbon connections. Used as a spawn preflight: if the consist's length can't be walked off the spawn point, the track runs out / hits a null ribbon and SpawnFormation would access-violate reading UNetworkRibbon::RibbonWidth (0x154).
UObject* WorldContextObjectFNetworkRibbonLocation InLocation (0x1C)float InDistanceCMEDirectionOfTravel InDir (u8)FNetworkRibbonLocation& OutLocation (out)bool& OutSuccess (out)float& OutDistanceRemaining (out; >0 = ran off)(dump 2026-07-17)
Sandbox.hpp:136ASandboxManager::GetStateESandboxManagerState: 0 Idle, 1 SpawningTrains, 2 RemovingTrains, 3 SettingPath, 4 RemovingPath, 5 AddingWaypoint. Attribution signal: the menu spawns while state == 1.
Sandbox.hpp:157ASandboxManager::StartRemovingTrainThe menu's train-removal entry point; candidate despawn path for TSM-spawned consists.
URailVehicleComponent* RemoveTrainCandidate(dump 2026-07-17)
Sandbox.hpp:164ASandboxManager::IsRibbonLocationValidForSpawnThe game's OWN spawn-room validator — the check the free-roam menu runs (which is why the menu never crashes). Given a ribbon location + direction, returns whether a train can spawn there without laying a car off the network. The reliable replacement for the AdvanceTrackLocation heuristic IF it doesn't fatal-check when called outside the menu flow (a prior agent recorded that it might — probe with `spawn valid` first).
FNetworkRibbonLocation InRibbonLocation (0x1C)EDirectionOfTravel InLocalDirection (u8)bool ReturnValue(dump 2026-07-21) — re-probe post-priming (TPM T4)
Sandbox.hpp:171ASandboxManager::SetTrainEntrySet the manager's CURRENT train entry — the menu's first step. Takes the full FCachedTrainEntry (0x1B8); TSM replays one captured from the menu pick so IsThereRoomForCurrentTrainEntry becomes a valid gate.
FCachedTrainEntry NewTrainEntry (0x1B8)bool ReturnValue(dump 2026-07-21)
Sandbox.hpp:185ASandboxManager::SetSpawningStateEnter the SpawningTrains state (GetState -> 1). THE setup the free-roam menu does before spawning — it makes the spawn subsystem consistent (creates/enables the SpawnPlaceholder). Driving SpawnFormation/room-checks while Idle instead is what crashes intermittently (2026-07-21). Pair with SetIdleState.
bool ReturnValue(dump 2026-07-21)
Sandbox.hpp:195ASandboxManager::SetIdleStateReturn to Idle (teardown after a spawn). No params.
(dump 2026-07-21)
Sandbox.hpp:205ASandboxManager::IsThereRoomForCurrentTrainEntryRoom check for the manager's CURRENT entry (set via the placeholder/SetTrainEntry) at the current placement.
bool ReturnValue(dump 2026-07-21)
Sandbox.hpp:210ASandboxManager::IsThereRoomForTrainEntryRoom check for a specific entry at the manager's current spawn location. More menu-state-dependent than the ribbon-location form (uses internal state), so likelier to fatal-check cold.
UTrainEntryDefinition* TrainEntrybool ReturnValue(dump 2026-07-21)
Sandbox.hpp:217ASandboxManager::SetDestinationForRVThe free-roam "drive to destination" path (candidate M4b): the game's AI drives the consist to a ribbon location for us.
URailVehicleComponent* RailVehicleFNetworkRibbonLocation SelectedRibbonLocation (0x1C)(dump 2026-07-21)
Sandbox.hpp:226ASandboxManager::AddWaypointsForRVPathURailVehicleComponent* RailVehicleFNetworkRibbonLocation SelectedRibbonLocation (0x1C)(dump 2026-07-21)
Sandbox.hpp:234ASandboxSpawnPlaceholder::SetTrainEntrySet the placeholder's entry — takes a PLAIN UTrainEntryDefinition (no FCachedTrainEntry needed, unlike the manager's SetTrainEntry).
UTrainEntryDefinition* NewTrainEntry(dump 2026-07-21)
Sandbox.hpp:250ASandboxSpawnPlaceholder::RecalculateVehiclePlacementsTransformLay out the per-car placements for the entry at a ribbon location — the menu's setup step before the room check/spawn.
FNetworkRibbonLocation InRibbonLocation (0x1C)EDirectionOfTravel InLocalDirection (u8)(dump 2026-07-21)
Sandbox.hpp:257ASandboxSpawnPlaceholder::EnableEnable the placeholder (the menu enables it while positioning).
(dump 2026-07-21)
Sandbox.hpp:265TS2TimetableRouteMapWidget — the timetable/route map UI widget. From the spike these route functions are the current lead on identifying the loaded route (for the server's `map` field and, later, refusing cross-route joins). Cataloged for the docs; route identity is not wired into TSM yet.
TS2TimetableRouteMapWidget::SetStartRoutePointsTS2TimetableRouteMapWidget::SetEndRoutePointTS2TimetableRouteMapWidget::SetPlayerRoutePointTS2TimetableRouteMapWidget::SetRouteMapPointsInteractableTS2TimetableRouteMapWidget::ShowRouteLinesATrackNetworkActor (AActor)Per-tile owner of a slice of the track network. FindAllOf by this class name; skip Default__ CDOs.
(dump 2026-07-21)
Track.hpp:23UPhysicsFunctionLibrary::GetRibbonUnderWheelUObject* WorldContextObjectFSimulationComponentNodeReference RefNode {FName Component, FName Node}float& T (out; ribbon location)EDirectionOfTravel& Direction (out; u8)UNetworkRibbon* ReturnValue(dump 2026-07-21) — RefNode source unresolved
Track.hpp:89ATrainSpawner (AActor)Self-contained consist spawner. FindAllOf / find_class_by_name by this class name; spawn a fresh instance (no pre-placed one exists in free-roam).
TrainSpawner.hpp:44ATrainSpawner::SpawnConsist (no params)Spawn the consist for TrainDefinition at this actor's transform.
(0x154 on off-network placement; retry with a Track-validated transform)
TrainSpawner.hpp:60ATrainSpawner::DespawnConsist (no params)Despawn the consist this spawner made — candidate clean despawn primitive (SandboxManager::StartRemovingTrain is still unverified).
TrainSpawner.hpp:65UVirtualHIDComponent::SetNormalisedInputValueDrive this control to `Value`, a [0..1] FRACTION OF THAT LEVER'S OWN RANGE — not a raw input value. The two differ per control: Reverser range [0..1] -> set 0.67 gives in=+0.67 (1:1) PowerHandle range [-1..+1] -> set 0.25 gives in=-0.50 (in = 2v-1) Measured 2026-08-07, reproducible. So on a combined power/brake handle, v=0.5 is NEUTRAL, v<0.5 is brake and v>0.5 is power; the player's own driving position of in=+0.25 is v=0.625, and full power (in=+1.00) is v=1.0. Asking for "0.25 power" gets you half brake, which is exactly the trap we fell into.
float Valueworking 2026-08-07 — MasterKey, Reverser and PowerHandle all set and hold. Match the control name EXACTLY: a substring match on "PowerHandle" also hits "PowerHandle_BrakeHold" and pins the brake on with the same command that asks for power.
VirtualHID.hpp:37UVirtualHIDComponent::SetPositionDeltaAnalogueTHE PLAYER INPUT PATH. Moves the lever by DeltaMovement on behalf of a Controller, which is what a human hand does — so it runs the whole bIsChanging / CurrentChangingController / OnBeginChanging -> InputValueChanged -> OnEndChanging cycle that actually pushes the new position into the simulation.
AController* Controller (the local PlayerController)float DeltaMovementas an input path, 2026-08-07 — but only while a controller is registered as holding the lever: forge CurrentChangingController + bIsChanging, apply deltas, then let go. Notched levers (Reverser, MasterKey) snap back from a step too small to clear their detent, so escalate the step when a lever stops responding. Prefer this over SetNormalisedInputValue, which writes CurrentInputValue while the sim never reacts and leaves even MANUAL driving braked afterwards.
VirtualHID.hpp:54UVirtualHIDComponent::InputValueChangedThe notification half. If a raw set has to be told to propagate, this is the call that does it.
float OldValue, float NewValue