Skip to content

(Experimental) PID sync, axle-based remote steering, cluster sync for coupled vehicles and other things#188

Closed
spectrachrome wants to merge 20 commits into
TheHellBox:masterfrom
spectrachrome:desync-investigation
Closed

(Experimental) PID sync, axle-based remote steering, cluster sync for coupled vehicles and other things#188
spectrachrome wants to merge 20 commits into
TheHellBox:masterfrom
spectrachrome:desync-investigation

Conversation

@spectrachrome
Copy link
Copy Markdown

@spectrachrome spectrachrome commented Apr 4, 2026

Screenshot 2026-04-11 at 06 48 42

Trailer & coupled-rig multiplayer sync

Added

  • Full coupled-rig synchronization for trucks, trailers, and chains.
    Coupled trailers run on their physics coupler constraint while the
    GE side computes an expected hitch-point position for drift detection
    and debug visualization.
  • Hitch-type branching for sync behavior: fifth-wheel (yaw-only
    articulation), ball hitch (3-DOF quaternion), and pintle (loose
    3-DOF with wider drift tolerance). Classified once at attach time
    from the coupler tag and cached on the coupling.
  • Three-way coupler event capture to close BeamNG event-dispatch gaps:
    per-vehicle kiss_couplers.onCouplerAttached hook, GE-level
    onCouplerAttached dispatch, and onObjectCouplingChange diff of
    attachedCouplers as a belt-and-suspenders fallback.
  • Time-based dedup on outbound coupler events so the three capture
    paths don't send duplicates.
  • Inverse-index tracking (coupled_trucks) so GE-side sync can look
    up a truck's trailers and know when to skip try_rude or apply
    coupled-specific scaling.
  • Cluster-aware teleport: when any member of a coupled cluster drifts
    past the threshold, the whole cluster is translated rigidly to
    preserve relative pose and keep the coupler constraint intact.
  • Graph walk over coupled_to / coupled_trucks so the cluster
    discovery works for road trains transparently.
  • Per-vehicle mass reporting from vehicle Lua to GE so coupler sync
    can size PD gains by the truck/trailer mass ratio at attach time,
    plus a mass-based scaling of the coupled rotation gain itself.
  • 3-second decouple grace period after coupled_to is cleared,
    skipping full PD sync during the brief detach→re-attach window
    (e.g. after a reset) so the physical coupler isn't ripped before
    the re-attach event arrives.
  • kiss_electrics.set_coupled state tracking on vehicle side so
    advanced coupler electrics only fire when actually coupled via
    our tracking. Remote advanced couplers are force-detached at
    spawn to prevent auto-attachment to nearby objects.
  • Retry loop for tryAttachGroupImpulse over a 500 ms budget so
    advanced couplers reliably retract landing gear after the async
    C++ latch completes.
  • Parking brake release on the trailer at attach time, since trailers
    don't send VehicleUpdate and wouldn't otherwise get input sync.
  • coupler_tag field on CouplerAttached network message so the
    remote can classify hitch type from the same event.
  • Axis-swizzle conversion in the coupled-truck PD so network angular
    velocity (packed pitch, roll, yaw) is subtracted against local
    angular velocity (built yaw, pitch, roll) in matching slots.
  • Debug visualization on the vehicle-side PD (force vectors, drift
    line, angular-force indicator, expected/received/current spheres,
    drift threshold markers) gated by M.debug.
  • Tick-paced debug logging of per-vehicle sync state gated by
    M.debug_log.

Changed

  • Coupled-truck rotation PD now uses coupled_ang_force = 2 (1/50 of
    the non-coupled gain) with a hard cap of 3, giving a proper
    restoring force on all three axes without whipping the hitch
    per tick.
  • try_rude (6 m per-vehicle teleport) is skipped for coupled trucks
    because it would yank the rig apart under trailer drag lag. Drift
    is handled cluster-wide instead.
  • kiss_gearbox.apply is always called on synced vehicles; the former
    if not coupled_to[id] gate was removing gear sync from the truck
    side after coupling.
  • Ownership guard on inbound coupler attach/detach events now checks
    both vehicles, not just obj_a, so rebroadcast events don't
    reposition our own rig.
  • Inbound attach_coupler is idempotent on re-received events for the
    same pair so duplicates don't disturb the active constraint.
  • Vehicle spawn cleanup: known_couplings, coupled_to, and
    coupled_trucks state is cleared on mission load and vehicle
    destruction.

Fixed

  • Jackknife on coupled rigs caused by the full non-coupled rotation
    gain (100) whipping the hitch node per tick.
  • Second trailer in a chain flipping around its longitudinal axis on
    asymmetric drives — network yaw rate leaking into the remote's roll
    slot via mismatched angular-velocity packing.
  • Coupler ripped out of the truck on every reset, often throwing the
    chain too far to re-latch.
  • Truck gearbox permanently frozen on the remote at whichever gear
    was active at attach time.
  • Articulated bus and other long rigs breaking themselves apart /
    flipping at speed, from an experimental hitch-pivot rotation that
    injected energy through the wrong axis.
  • Light rigs over-rotating and heavy rigs under-rotating because the
    coupled-truck rotation gain was mass-invariant.
  • Owned vehicles yanked sideways by rebroadcast coupler events bouncing
    back to the sender.
  • Angular acceleration never clamped (the guard was checking linear
    acceleration twice), causing big ang-accel spikes to pass through
    unfiltered on lossy networks.
  • Self-coupling events from fifth-wheel hitches without a trailer
    being sent as real couplings.
  • Ball-hitch L-key couplings that the vehicle-side callback missed
    now land via the GE-level onCouplerAttached dispatch or the
    onObjectCouplingChange diff.
  • Advanced coupler electrics grabbing random nearby objects on
    remote vehicles at spawn, since their notAttached state was
    being driven by synced values.

Known issues

  • Systematic trailer drift under sustained correction.
  • Cars and loose cargo on tilt decks sliding off, mostly downstream
    of trailer drift.
  • Articulated bus back-end flex and doors popping under PD forces.
  • Low fifth-wheel hitches detaching on reset because the trailer's
    landing gear drops below the hitch height before the coupler
    can re-latch.

add a dedicated sync path for coupled trailers that uses angular
correction relative to the truck's rotation, with a gentle linear
nudge to keep the trailer positioned correctly.

previously, trailers used the same full PD sync as regular vehicles,
which caused physics explosions and hitch detachments. the new approach
lets the coupler constraint handle most positioning while only
correcting the hitch angle and providing minimal position assistance.

key changes:
- track trailer-truck coupling relationships via coupled_to table
- separate sync path for coupled trailers (angle + gentle nudge)
- sync parking brake and inputs to trailers (fixes stuck landing gear)
- prevent fifth wheel hitch from locking remote vehicles via
  advancedCouplerControl guards and force-detach on spawn
- filter self-coupling and duplicate coupler events
- allow linear forces even when angular correction is skipped
- add nil safety for kiss_transforms commands during vehicle loading
- add debug visualization spheres and detailed sync logging
the local was declared after onExtensionLoaded, so table.insert hit nil
and killed initialization for every hitched vehicle. this broke electrics
handlers for engine state, lights, horn, abs, and more.

also add mass-relative scaling for coupled trailer forces so heavier
trailers get proportionally more correction and lighter ones don't
get pushed into a v-shape at the hitch.
@spectrachrome spectrachrome changed the title Trailer Fix Trailer Desync Fix Apr 4, 2026
@spectrachrome spectrachrome changed the title Trailer Desync Fix Trailer Fix Apr 4, 2026
Replace velocity-matching trailer sync with a constraint-preserving approach:
GE side computes expected trailer CG from hitch geometry (fifthwheel=yaw-only,
ball/pintle=full quaternion), vehicle side does threshold-based hard snap only.
Removes tow_boost in favor of per-hitch-type drift thresholds. Transmits
coupler_tag through the server so remote clients can classify hitch type.
@spectrachrome spectrachrome changed the title Trailer Fix Hitch-Dependent Cluster Sync for Coupled Vehicles Apr 7, 2026
@spectrachrome spectrachrome changed the title Hitch-Dependent Cluster Sync for Coupled Vehicles Hitch-dependent cluster sync for coupled vehicles Apr 7, 2026
@spectrachrome spectrachrome changed the title Hitch-dependent cluster sync for coupled vehicles Hitch- and mass-dependent cluster sync for coupled vehicles Apr 7, 2026
@spectrachrome spectrachrome changed the title Hitch- and mass-dependent cluster sync for coupled vehicles Hitch and mass aware cluster sync for coupled vehicles Apr 7, 2026
@spectrachrome spectrachrome changed the title Hitch and mass aware cluster sync for coupled vehicles Mass-aware, hitch-dependent cluster sync for coupled vehicles Apr 7, 2026
@spectrachrome spectrachrome changed the title Mass-aware, hitch-dependent cluster sync for coupled vehicles Hitch-dependent cluster sync for coupled vehicles Apr 7, 2026
@florinm03
Copy link
Copy Markdown
Contributor

Cargo.lock and claude is not needed on the repo, make sure to only push what's necessary ;)

…stics

Remove the constraint-preserving coupled sync (expected position + snap)
that fought BeamNG's coupler physics and caused accumulating jackknife
drift. Coupled trailers now use the same PD sync as all vehicles — the
physical coupler constraint naturally overrides PD forces.

Defer coupler attachment on remote by 1-2s to allow vehicles to fully
initialize before firing attachCoupler. Buffer CouplerAttached network
events that arrive before vehicles are spawned (id_map miss) and retry.

Additional changes:
- Reactivate trailer advancedCouplerControl on coupling (landing gear)
- Attach coupler from both trailer and truck sides
- Rotate node offsets to world-space for pre-attach positioning
- Increase coupler search radius to 1m for remote attach
- Add diagnostic logging for coupler events
- Disable per-frame debug spam (debug/debug_log = false)
- Remove electrics send logging
@spectrachrome
Copy link
Copy Markdown
Author

spectrachrome commented Apr 9, 2026

Sorry, of course! I'll ditch the CLAUDE.md and lock file in the next commits and generally clean this up. Old habits :)

Also going to look into the many merge conflicts.

Coupled trucks and trailers now rely on the physics coupler constraint as
the sole source of truth for their relative pose. No path can teleport a
coupled vehicle anymore, and the truck's angular PD no longer whip-cracks
the hitch.

kisstransform / kiss_transforms:
- Route coupled trucks to update(dt, true, ang_scale) via new
  coupled_trucks inverse index in vehiclemanager.
- update(dt, skip_rude, ang_scale): skip try_rude entirely for coupled
  trucks (the 6m teleport was yanking the whole rig under PD lag). Rewrite
  the angular_force formula to drop the proportional angle-error term
  (angle_delta_euler * ang_force) — that term was the jackknife driver
  because it slammed a rigid-body rotation impulse into the chassis every
  tick, whip-cracking the hitch faster than the trailer pivot could absorb.
  Keep only angular velocity matching + damping, which gives smooth curve
  tracking without the per-tick snap.
- update_coupled: drop the setPositionNoPhysicsReset snap entirely. The
  coupler constraint now holds the trailer.

vehiclemanager (attach_coupler / detach_coupler):
- Guard early-return on ownership of EITHER side of the pair (was only
  checking obj_a), so inbound rebroadcasts can't yank a vehicle we own.
- Make attach_coupler idempotent: skip snap + reattach when pair is
  already in coupled_to, so duplicate messages become no-ops.
- Cache hitch offsets in vehicle-local space (inverse_rot *
  getNodePosition), not the world-rotated value getNodePosition returns
  directly. update_coupled's current_rot * offset math was double-rotating
  whenever the vehicle wasn't aligned with world +Y at attach time,
  placing the expected-position marker far from the real trailer.
- Add coupled_trucks inverse index (truck_id → trailer_id), cleared on
  detach, vehicle destroy, and mission load.
- Add vehicle_masses table + set_vehicle_mass setter. Compute
  ang_scale = clamp(truck_mass / (truck_mass + trailer_mass), 0.05, 0.5)
  at attach time and store it on the coupling. Not currently consulted
  vehicle-side (the rewritten angular formula made it redundant) but
  plumbed end-to-end for future use.

kiss_vehicle:
- onExtensionLoaded reports the computed total jbeam mass to GE via
  vehiclemanager.set_vehicle_mass so the ang_scale computation at attach
  time has real data.
@spectrachrome spectrachrome changed the title Hitch-dependent cluster sync for coupled vehicles Cluster sync for coupled vehicles Apr 10, 2026
Coupled ghost trucks with bad ping or physics divergence accumulate
heading drift that projects into lateral position drift. Per-tick
angular PD can't correct this: any proportional angle term (even 1/50
of the non-coupled gain) whip-cracks the hitch node because rigid-body
chassis rotation generates tangential velocity at the hitch the trailer
pivot can't absorb smoothly. Without correction the rig just drifts.

Move the drift-recovery decision to GE-side and apply it atomically to
the whole cluster as a rigid-body snap rather than a per-vehicle
teleport. Per-vehicle was already disabled for coupled trucks because
it rips the hitch apart.

kisstransform.lua:
- Walk coupled_to / coupled_trucks graph from any cluster member via
  get_cluster_members; supports road trains transparently.
- In the update loop, for any vehicle in a cluster whose world drift
  exceeds CLUSTER_TELEPORT_THRESHOLD (8m), compute the truck's pose
  delta (position + rotation) and apply a rigid-body rotation of the
  whole cluster around the truck's current position onto the truck's
  target position/rotation:
    new_pos = pivot_target + rot_delta * (member_pos - pivot_current)
    new_rot = rot_delta * member_rot
  This preserves relative pose between truck and trailer so the coupler
  constraint is not disturbed, while atomically correcting the heading
  error that the per-tick PD could not touch.
- Rotation snap was critical: a translation-only snap left the rig
  facing the wrong direction, so lateral drift immediately re-
  accumulated and teleports fired continuously. With rotation, each
  snap is a true reset and the next teleport is rare.
- Per-cluster cooldown (1.5s, keyed by min member id) prevents snap
  thrashing during rapid successive drift bursts.
- Solo non-coupled vehicles unchanged — still use vehicle-side try_rude
  at 6m.

kiss_transforms.lua:
- Coupled-truck branch of update() remains pure velocity matching +
  damping. Empirically confirmed any proportional angle term, even at
  1/50 gain with a magnitude cap, reintroduces jackknife because the
  mechanism (rigid-body chassis rotation) is the problem regardless of
  magnitude.
advancedCouplerControl trailers (tilt beds, most modern rigs) left
their landing gear extended after ball-hitch coupling unless the user
manually reset the vehicle in place. Root cause: tryAttachGroupImpulse
is called from the electrics handler on the ghost the moment the owner
flips the notAttached electric, but the C++ coupler latch is
asynchronous. If the impulse call races ahead of the latch, the
controller's state machine refuses to run its connect sequence
(including leg retract) because it doesn't think it's attached yet,
and subsequent electrics packets carry the same value so the handler
never re-fires. A manual reset re-latched and re-triggered the path.

Schedule up to 5 re-invocations of tryAttachGroupImpulse at 100ms
intervals after the initial call (~500ms retry window, comfortably
past the ball-hitch search poll period). On detach, cancel any
pending retries for that controller so a quick connect→disconnect
sequence doesn't leave a stale job re-latching against user intent.

Also add a best-effort is_controller_attached probe that sniffs
likely field/method names (notAttached, attached, groupAttached,
isAttached()) and drops the pending entry as soon as the controller
reports attached, to minimize redundant impulse calls in the common
case. If the field sniff misses (unknown controller API), falls
through to the full 5-attempt budget — no regression vs. the
pre-guard behavior.
- Pivot the truck's PD angular correction around the hitch node instead of
  the reference node, so heading correction produces zero tangential velocity
  at the coupling point and doesn't side-slap the trailer chain.
- Yaw-only proportional term via forward-vector math (avoids ambiguous
  toEulerYXZ components bleeding roll/pitch into the rotation).
- Speed-inverse and mass-linear gain scaling: gain grows with truck mass
  (light rigs no longer over-rotate) and shrinks with speed (KE scales with
  v² so per-tick rotation energy stays bounded across the speed range).
- 3-second grace period after a vehicle loses its coupled_to entry: skips
  full PD sync during the brief detach→re-attach window (e.g. after a reset)
  so ang_force=100 doesn't rip the physical coupler apart before the
  re-attach event arrives.
- Remove the `if not coupled_to[id]` gate around kiss_gearbox.apply. The gate
  froze the truck's gearbox at whatever gear was active at attach time;
  kiss_gearbox.apply is already a no-op on trailers (no mainController).
- kiss_electrics: retry tryAttachGroupImpulse with a 500ms budget so
  advanced couplers reliably retract landing gear after the async C++ latch
  completes.
@spectrachrome spectrachrome changed the title Cluster sync for coupled vehicles PID sync, axle-Based remote steering, cluster sync for coupled vehicles and other things Apr 14, 2026
@spectrachrome spectrachrome changed the title PID sync, axle-Based remote steering, cluster sync for coupled vehicles and other things PID sync, axle-based remote steering, cluster sync for coupled vehicles and other things Apr 14, 2026
…ate fix

Replaces the impulsive angular-torque sync path on coupled rigs (and
optionally on solo vehicles) with a lateral force applied at a
precomputed front-chassis node subset. The chassis yaws via real beam
dynamics and tire slip instead of per-tick velocity snaps at every
node, eliminating the hitch whip that made trailer rigs unusable.

Core architecture:

- New force primitive: apply_force_at_front_chassis distributes a total
  world-space force across a mass-weighted subset of non-wheel chassis
  nodes ahead of the CG. Resultant acts at the subset centroid
  (~35% of vehicle length forward of center). Uses real node mass
  (new node[5]) rather than the FPS-scaled legacy node[2], so the
  primitive applies true Newtons via applyForceVector rather than
  abusing it as a velocity-impulse setter.

- front_chassis_subset built at onExtensionLoaded for every vehicle:
  filters nodes by y > y_center (engine-local forward of CG) and
  excludes wheel nodes. Top 6 closest to the target point.

- Coupled-truck branch in kiss_transforms.update() rewritten: forward-
  vector yaw error via atan2(cross_z, dot), speed-gated PD signal
  applied as lateral force via the new primitive. Zero angular torque
  on coupled rigs — apply_linear_velocity_ang_torque is never called
  from this branch. Early return prevents fallthrough to legacy PD.

- Solo vehicles can opt into the front-puller via use_front_puller_solo
  toggle. Their vehicle mass is looked up via vehiclemanager.vehicle_masses
  and passed as truck_mass.

PID lateral self-centering:

- Added M.lateral_integral state with anti-windup clamp (±5 m·s).
  Accumulates position error perpendicular to heading each tick;
  Ki*integral force eliminates steady-state drift that P alone can't
  close. Reset on cooldown and try_rude teleport.

- Yaw-rate-coupled lateral P scaling: lateral_pd_scale is now a
  "mid-turn floor" that ramps back to 1.0 on straights, soft during
  hard corners. Auto-balances P authority to the current driving
  regime without manual tuning.

Prediction fixes:

- Kinematic position extrapolation now correct: p0 + v0·t + ½·a·t²
  instead of end-velocity × t (which double-counted acceleration).

- Axis-angle rotation extrapolation: q_delta built directly from the
  angular velocity vector via axis-angle construction, avoiding the
  BeamNG quatFromEuler/toEulerYXZ slot-convention trap. Right-
  multiplied onto body rotation, normalized each tick.

- Acceleration clamp raised from 5 to 15 m/s² (tunable). The old
  limit threw away centripetal acceleration during meaningful
  cornering, making the prediction target lag the real curve.

- Split prediction time caps: 100ms linear, 200ms rotational. Normal
  latency passes through unclipped; only pathological staleness gets
  capped.

Live tuning UI (kisstuning.lua + kissmp/ui/tabs/tuning.lua):

- New GE-side extension holding 10 tunables with label/range/desc
  metadata. Registered in modScript.lua before kissui so the global
  is available at UI draw time.

- New tuning tab in the main window. Sliders for floats, checkboxes
  for bools. Values pushed into every kiss_transforms.update call as
  trailing args, so slider changes take effect next physics tick
  without rebuilding the mod.

- Tunables: Kp_yaw, Kd_yaw, force_cap_accel, speed_gate_low,
  speed_gate_high, accel_clamp, lateral_pd_scale, lateral_integral_gain,
  deadband_enabled, use_front_puller_solo.

- Local-only. No network sync — each player tunes their own remote
  rendering. Intentional for a dev/testing feature.

Spectate input race fix (kiss_input.lua):

- Monkey-patches input.event (and kbdSteer/kbdPedal if present) on
  every vehicle Lua VM to no-op on non-owned vehicles unless a
  sync_in_progress guard is set. Blocks local joystick/keyboard
  input from racing against network-synced input on multi-instance
  A/B test rigs — a major hidden source of "drift" that PD tuning
  couldn't reach.

Status: drivable but imperfect. Trailer rigs no longer disassemble
on spawn or during driving. Articulated bus city routes complete
without destruction. Residual drift (~0.5-1m over long distances)
remains from physics simulation divergence between owner and remote
that no amount of force-based sync can fully eliminate. The PID
integral term closes steady-state drift at rest; the auto-balanced
P closes transient drift during straights; the front-puller handles
heading correction via tire dynamics during motion. Parked heading
can still be a few degrees off due to the speed gate cutting out
before full convergence.

Files touched:

- KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_vehicle.lua
- KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_transforms.lua
- KISSMultiplayer/lua/vehicle/extensions/kiss_mp/kiss_input.lua
- KISSMultiplayer/lua/ge/extensions/kisstransform.lua
- KISSMultiplayer/lua/ge/extensions/kisstuning.lua (new)
- KISSMultiplayer/lua/ge/extensions/kissmp/ui/tabs/tuning.lua (new)
- KISSMultiplayer/lua/ge/extensions/kissmp/ui/main.lua
- KISSMultiplayer/lua/ge/extensions/kissui.lua
- KISSMultiplayer/scripts/kiss_mp/modScript.lua
…n fit, OBB debug overlay

Stage 1 Phases 0-2 of the cluster sync roadmap. Single-vehicle
single-cluster degenerate case: each vehicle is treated as one rigid
cluster; owner broadcasts pose via the existing transform channel;
remote applies per-node velocity-matching forces to converge.

New vehicle-side modules (kiss_mp/cluster_*.lua):
- cluster_state: data structures + tuning constants (CONVERGENCE_GAIN, KP_POS)
- cluster_discovery: semantic-seeded stiffness-thresholded connected components
- cluster_topology: parent-child BFS tree + deterministic topology hash
- cluster_frames: mass-weighted centroid, per-node offsets, inertia tensor
- cluster_spawn: v.data/obj adapter, region extraction via partOrigin
- cluster_math: Horn's quaternion method via Jacobi 4x4 eigendecomposition
- cluster_sender: per-cluster pose synthesis (pos, rot, lin_vel, ang_vel)
- cluster_receiver: per-node velocity-matching force with position spring
- cluster_debug_draw: OBB wireframe (rotates with chassis), region overlay

Integration in kiss_transforms.update():
- try_rude (6m teleport snap) runs first with 0.5s cooldown to prevent
  beam-breaking forces after owner map-teleports
- try_apply_cluster_sync branch: gated on cluster_sync_enabled, reads
  target from existing transform channel, applies per-node forces for
  root clusters, early-returns before legacy code
- Falls through to legacy front-puller/PID if cluster sync unavailable

Tuning tab additions:
- cluster_sync_enabled (bool, default ON for testing)
- cluster_sync_force_fallback (bool, A/B diagnostic toggle)
- cluster_convergence_gain (0.05-1.0, default 0.3)
- prediction_enabled (bool toggle)
- sample_rate_multiplier (outgoing packet rate)
- Cluster debug overlay view mode switcher (off/clusters/regions/both)

Also includes:
- 105 pure-Lua TDD tests (tests/lua/)
- Accel smoothing in set_target_transform (70/30 exponential filter)
- Electrics log spam removed
…ndividual poses

Extends the cluster sync pipeline to broadcast per-cluster pose data
for vehicles with more than one cluster (e.g. pickup with separate
rear bumper cluster). Single-cluster vehicles continue using the
whole-vehicle transform as before (empty clusters vec).

Rust schema (requires server rebuild):
- ClusterPose struct in shared/src/vehicle/transform.rs: id, position,
  rotation, linear_velocity, angular_velocity (all world-frame)
- VehicleUpdate.clusters: Vec<ClusterPose> with #[serde(default)]

Owner send path (kiss_vehicle.lua):
- When cluster_state has >1 cluster, compute_all_poses via
  cluster_sender (Horn rotation + angular momentum) and attach
  compact cluster pose array to the transform blob

GE relay (vehiclemanager.lua → kisstransform.lua):
- send_vehicle_update maps compact field names to wire format
- update_vehicle_transform attaches clusters to the transform JSON
  forwarded to the vehicle side

Vehicle receive (kiss_transforms.lua):
- set_target_transform stashes received_cluster_poses keyed by id
- try_apply_cluster_sync looks up per-cluster pose for each cluster;
  falls back to whole-vehicle transform for root cluster when no
  per-cluster data available (Phase 2 compat)
- Removed is_root gate: all clusters with a received pose get forces
The server stores and relays per-cluster poses but the Vehicle struct,
spawn initializer, and incoming VehicleUpdate handler were missing the
new clusters field. Fixes compilation error on server rebuild.
…r constants

Sender converts non-root cluster poses to parent-relative before
sending; receiver reconstructs world poses by composing parent-first
in BFS order. Preserves hinge angles exactly (articulated bus, wheel
spin) instead of two independent world targets that can disagree.

Added parent_id field to ClusterPose (Rust schema change — rebuild
server + bridge).

New tuning sliders (live push to vehicle side):
- Position spring (KP_POS): 1-100, default 50
- Max force per node: 1k-200k N, default 50k
- Stiffness threshold: 50k-5M N/m, default 500k
- Min cluster size: 3-50 nodes, default 8
- Re-cluster button: re-runs discovery without vehicle reset
@spectrachrome spectrachrome changed the title PID sync, axle-based remote steering, cluster sync for coupled vehicles and other things (Experimental) PID sync, axle-based remote steering, cluster sync for coupled vehicles and other things Apr 16, 2026
@Vlad118 Vlad118 mentioned this pull request Apr 18, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants