Commit Graph
19 Commits
Author SHA1 Message Date
zwitschi 387a4530de test(vehicle): clamping the velocity to zero does not stop the rise - the transform is written
The decisive clamp: zero linear and angular velocity every frame for two seconds.

  t=0.25 Z=3220 vZ=0 | t=0.75 Z=3683 vZ=0 | t=1.25 Z=4197 vZ=0

It rises at ~1200 cm/s with velocity pinned to zero, so this is not a force, an impulse, or a bad initial
state - all of those the clamp would have removed. Something calls a transform setter on the vehicle every
frame.

Our own source contains exactly one direct transform write outside the character (the shot actor's camera),
so the writer is engine-side: the Chaos vehicle component driving UpdatedComponent from a vehicle state that
was never created, or a skeletal-mesh transform update.

Also diffed the two maps, since the pawn works in ReviewMap and not in DemoMap: kill_z, gravity, lights and
partitioning are identical; DemoMap adds 593 static mesh actors, a height fog and a post-process volume,
ReviewMap adds a shot actor and the character. Nothing in either list can write a transform, so the map
difference is not in its actors or settings.
2026-11-09 22:11:13 +01:00
zwitschi 331e2016be test(vehicle): the Chaos rig is fine - DemoMap is what breaks it
Ran the bisect the task asked for. The same pawn, unchanged, in two maps:

  ReviewMap, flat floor    Z 200 -> 188 -> 174 -> 157 -> 87   vZ -208 -> -663   falls correctly
  DemoMap, on the street   Z 486 -> 920 -> 1126 -> 1385      vZ ~0             teleports up
  DemoMap, 60 m away       Z 291 -> 467 -> 920 -> 1343       vZ +1595          launches
  DemoMap, 25 m up         Z 2496 -> 2664 -> 3068 -> 3434    vZ +1509          launches at once

So the pawn, its physics asset, its wheels, its mass and its movement component all work: on a plain floor
it falls and settles. Everywhere in DemoMap it is thrown upward, at any height, including 25 m in the air,
which rules out contact and points at something the demo map stages and the review map does not.

That inverts the previous nine hypotheses - they were all about the vehicle - and puts the next probe in the
map: diff the staged actors and world settings between the two builders.

Pawn parked; the bisect numbers are in the task.
2026-11-09 22:07:38 +01:00
zwitschi 358673bcfc test(vehicle): the body is genuinely dynamic, so the teleport is applied to a normal actor
The dynamics probe rules out the last structural explanation:

  simulate=1  instance=yes  profile=PhysicsActor

A normal dynamic actor - and it still travels ~700 cm/s upward with vZ oscillating around zero. So the
teleport is applied to a genuinely dynamic body, which is why not one of the force-shaped settings
(gravity, mass, penetration, spawn height) changed anything.

Recorded with it: the bisect plan for the next pass, because guessing at Chaos internals has stopped being
the cheap option. Spawn the pawn in ReviewMap's empty floor to separate asset from world, and spawn a stock
AWheeledVehiclePawn beside it to separate our setup from the engine's.

Pawn parked; captures unchanged.
2026-11-09 21:58:37 +01:00
zwitschi d1636f6986 test(vehicle): it is a kinematic teleport, not a force - and that is why every force test was clean
Instrumented the movement component so the write is caught at its source rather than inferred from the
actor: a UGigLineVehicleMovement subclass is substituted for the stock component through
AWheeledVehiclePawn::VehicleMovementComponentName, and it logs CreateVehicle(), the first UpdateState and
the component tick.

  mover tick: Z 661  -> 1143 -> 1443 -> 1718 ..., output null, wheels 0
  physics OFF at 3 s: Z 2247, and it does not move for a whole second afterwards
  profile: t=0.25 Z=515 vZ=-8 | t=0.75 Z=920 vZ=-22 | t=1.25 Z=1287 vZ=173

Two conclusions:

1. The mesh's physics state IS what moves the actor - switching physics off freezes it at once.
2. It moves WITHOUT VELOCITY: position rises ~700 cm/s while vZ oscillates around zero. That is a
   kinematic teleport, not an impulse and not a force - which is exactly why gravity, mass and
   penetration all measured clean. The vehicle-state output is null throughout, so the teleport comes
   from the Chaos layer driving the body kinematically while the vehicle itself was never created.

Pawn parked, captures regenerated so the demo stays clean.
2026-11-09 21:51:52 +01:00
zwitschi 348ed1ac69 test(vehicle): the writer is not in the pawn's tick
Sampled the actor transform immediately before and after Super::Tick on every frame: it never changes
there. The 'inside Super::Tick' line never fires, yet the actor climbs 400-1400 cm/s.

With the earlier results that leaves exactly one consistent candidate: the Chaos movement component's own
tick or its async physics step, outside APawn::Tick. That also explains why SetComponentTickEnabled(false)
did not stop the climb and why the body's reported velocity is ~0 while the actor moves - the movement
component is writing a transform it took from a vehicle state that was never created.

Next step is a subclass of UChaosWheeledVehicleMovementComponent that logs UpdateState /
OnUpdateTransform together with the vehicle-state pointer, so the write is caught at its source rather
than inferred from the actor.
2026-11-09 21:44:17 +01:00
zwitschi 0f059a5c93 test(vehicle): bones match, terrain has no lid, and stopping the mover does not stop the climb
Three more measured facts:

  bones  VehicleRig root Wheel_FL Wheel_FR Wheel_RL Wheel_RR
  setups Wheel_FL Wheel_FR Wheel_RL Wheel_RR           -> exact match, mesh suspect ruled out
  mover  updated=VehicleMesh ticking=1
  stop   tick disabled at 2.5 s with the actor at Z 2001; at 3.0 s it is at Z 2258
  terrain CTF_USE_COMPLEX_AS_SIMPLE with 0 box elements -> no invisible lid to eject from

So the wheel bones are right, there is no proxy collision to fight, and disabling the mover's tick did not
stop the climb. That moves the suspect list to the pawn's own Tick/root: something writes the actor
transform every frame regardless of the physics body, and the 1 cm bounds are its fingerprint.

Pawn parked again; the diagnostic stays.
2026-11-09 21:41:36 +01:00
zwitschi 14ed01a4e6 test(vehicle): the body barely moves, so something else moves the actor
The motion probe splits the problem in two:

  velocity  -24, -182, -28 cm/s     (a body that cannot climb 9 m in 1.5 s)
  mass      1500 kg                 (the override DID apply)
  box       452 x 205 x 40          (correct)
  bounds    extent 1,1,1            (still degenerate)
  actor     Z 1393, spawned at 458

The physics body is nearly still while the actor is nine metres above its spawn. So this is not a
force problem at all - it is a transform problem: something writes the actor's transform that is
neither the body's velocity nor our code, which is also what the degenerate bounds are telling us from
the other side.

Next: find who writes that transform. Suspects are the Chaos movement component's UpdateState /
OnUpdateTransform and the component hierarchy - a pawn whose root is the mesh while the physics state
belongs elsewhere behaves exactly like this.

Pawn parked again so the demo stays clean.
2026-11-09 21:34:22 +01:00
zwitschi de83f990ac fix(review): mannequin down 2 cm; 8 and 9 approved
Owner review: rows 1, 2 and 3 show the mannequin about 2 cm too high; rows 8 and 9 show nothing wrong.

The staging height moves from 8 cm to 6 cm above the floor plane (floor_z on the mannequin's
sit_on_floor call, which now logs +6.0), and rows 8 and 9 are approved. Rows 1, 2 and 3 go back to
pending with the change noted in the table, since the image is what changed.

Captures regenerated.
2026-11-09 21:30:09 +01:00
zwitschi f29d9d7e49 test(vehicle): the chassis box and the gravity are both innocent
Four more things measured instead of guessed:

  chassis box        452 x 205 x 40, centre 0,0,-18  -> correct for the car
  world gravity      global_gravity_z = 0.0, and the character falls normally
  spawn height       5 m up: the pawn still climbs, so penetration is not the cause
  mass override      SetMassOverrideInKg at 0.5 s never logged

The one number that does not fit: the pawn's mesh component reports bounds extent 1,1,1 while
simulating and carrying a 452 cm collision box, and the actor drifts 4.8 m sideways while climbing
with no input. A body that simulates with correct collision but degenerate bounds is not the body the
movement component is driving, so the next question is which component owns it and whether
CreateVehicle() runs at all.

The pawn is parked again; the diagnostic logging stays (it is the tool that produced this), and the
evidence is in the 'Vehicle physics' task.
2026-11-09 21:06:26 +01:00
zwitschi be86ee6d73 test(vehicle): the launch is inside the vehicle, not around it
Three hypotheses tested and ruled out, each with its own run:

  spawn level with the road        -> actor climbs to Z 1993
  spawn 1.2 m above the road       -> climbs to 2131
  visual panels' collision removed -> climbs to 2066

So neither the spawn penetration nor the panels' overlap is pushing the pawn up; the force is inside
the vehicle. With zero wheels there is no suspension to blame either, which leaves the chassis body
itself - its box size against the mesh bounds, or the skeletal mesh's own collision fighting the
physics asset body.

The pawn is parked again so the demo carries no 18 m flying car, and the ruled-out list is recorded in
the 'Vehicle physics' task next to the two remaining suspects.
2026-11-09 20:59:35 +01:00
zwitschi 4498274328 test(vehicle): park the Chaos pawn and give the physics its own task
The pawn was found again after a cleanup removed its staging, and the diagnostic answers the
zero-wheel question with a bigger one: with the pawn present the actor is launched to Z 1993 cm - 18 m
above the ground it stands at 150 - and still reports 0 wheels.

  GigLine vehicle diag: mesh=1 simulating=1 physasset=yes setups=4 wheels=0
  GigLine vehicle: settled at Z 1993 cm, simulating=1

Wheels are only created inside CreateVehicle() when the physics state produced a vehicle, so the
chassis body is the suspect rather than the wheel setup: PA_Vehicle_Sedan has one box on the root bone,
and a body that starts intersecting geometry is the usual reason a vehicle pawn launches itself.

Parked rather than shipped: the pawn is not staged, the visible car stays as panels, and the physics is
'Vehicle physics - Chaos rig' in TODO.md with the evidence and three concrete steps. The demo no longer
carries an 18 m flying car in its captures.
2026-11-09 20:50:52 +01:00
zwitschi 4db33489e1 fix(review): raise the mannequin 8 cm, lower the player 2 cm, per the owner's notes
Owner review of the last set:

  1, 2, 8, 9  rejected - 'mannequin still too low'
  14          approved, but 'floating, only maybe 2 cm too high'

The mannequin correction had been a no-op: actor bounds on a freshly assigned skeletal mesh read as
zero, so sit_on_floor returned +2 cm while the soles sat level with the floor plane. It is now raised
8 cm, which puts them clearly above it, and the four rows are back to pending for re-review with that
noted.

The player start drops 2 cm (258 -> 256) for row 14.

Also repaired the four table rows: a cell-index mistake in the previous edit had emptied their item
links.
2026-11-09 19:57:20 +01:00
zwitschi fb5488c1df fix(review): stage objects by their measured bounds, and aim the vehicle cameras at the car
The review notes named the same class of problem in six rows: things sitting a little too low or too
high in the floor. They were all hand-typed staging heights, and every one was wrong by a different
amount - the mannequin's root is not at its soles, the resting cube's origin is not at its base, the
vehicle's wheels are 2 cm below its panel origin.

sit_on_floor() measures the actor's bounds and shifts it so the mesh's lowest point lands 2 cm above
the floor plane, and reports the correction it applied:

  MPFB character    +2.0 cm
  ScaleTest_A      -48.0 cm   (it was floating)
  vehicle wheels    +2.0 cm

The DemoMap vehicle shot was rejected as 'no vehicle in the scene': both vehicle cameras were still
aimed at the car's old position, so re-aiming them at Y -1450 brings it back into frame.

Status table notes record each fix; the hero-facade rework and the shadow-compare expectation are
left for the owner's next review.
2026-11-09 19:39:04 +01:00
zwitschi 1b4b46e957 fix(site): slice the road too - the 2 m lid was a convex hull, not geometry
The road was never mis-modelled: a 279 m strip imports with ONE auto-generated convex hull, and a
convex hull's top is the strip's maximum height. A downward trace therefore landed at 366 cm - a flat
lid about 2 m above the visible road, which sits at roughly 152 cm there. That single fact explains
every failed sidewalk fix, the 'road above its own sidewalk' reading, and why the player always ended
up on Road_Hollywood.

Slicing the road the way the sidewalks already were fixes it, and the cross-section is now continuous:

  Terrain_DEM       154
  Sidewalk_North_07 158
  Road_Hollywood_07 158   (was 366)

Two changes had to land together: the road exported in slices, and the map stopped staging the single
Road_Hollywood asset - which still sat on top of the slices and won every trace until removed.

All 23 captures regenerated; the player and the car both stand on a continuous ground plane.
2026-11-09 19:15:10 +01:00
zwitschi 4f9e92d975 fix(placement): both objects stand on the pavement, and the sun no longer separates them from their shadows
Three changes close the float:

- The sun went from pitch -74 to -82, so shadows fall under the objects instead of a metre away.
  That is what made grounded objects read as hovering, and the judge reacted to it in every run.
- The car is parked on the same sidewalk surface the player stands on (158 cm at this X) rather
  than on the carriageway, which the shipped site geometry places ~2 m ABOVE its own pavement - a
  data defect still open, but no longer visible in the review shots.
- The player already stands on the sidewalk since Y was moved to -1450.

The wheel and feet captures show the contact: feet and tyres on the pavement with their shadows at
the contact point. judge_screenshot still returns 'floating' for all three, which is advisory only -
AGENTS.md is explicit that it never replaces the human verdict - so it is recorded in the status
table as a disagreement rather than iterated against any further.

All 23 captures are regenerated and awaiting approval.
2026-11-09 19:05:41 +01:00
zwitschi b9919b8d1e fix(placement): the player stands on the sidewalk, at last - 32 cm was the whole float
The player start was at Y -1150, and the north sidewalk slice at that X spans Y -1734..-1182. He was
standing 32 cm south of it, on the roadway - which is why every trace named Road_Hollywood and every
sidewalk fix changed nothing. Moved to Y -1450 and the capsule now rests on the sidewalk:

  top surface under player: Z=158 mesh=Sidewalk_North_07 (actor Z 248)
  body: mesh bottom 161, capsule bottom 161

The cameras that frame the pawn moved with him, and the car now sits on the measured carriageway at
366 rather than the 342 it had.

The captures still read as floating in the vision judge's opinion, and the table says why honestly:
the placements measure correct, but at this sun angle the shadows fall well away from the objects,
which is what the judge is reacting to.

All 23 captures are regenerated and await a fresh approval.
2026-11-09 18:55:43 +01:00
zwitschi d0d87e8fda fix(site): the slice cuts now keep the middle, and the surfaces do not agree
Two findings, one a bug and one the real cause.

Bug: the far cut of each slice used clear_outer=True, which kept the wrong half, so every 'slice'
still spanned to the end of the street and inherited the same far-away bbox maximum - which is why
slicing appeared to change nothing. Both cuts now keep the middle, and the slices are disjoint:
their tops read 380, 340, 309, 284, 254, 231, 205, 169, 138, 104 cm along the street.

Real cause: at the player's X (-133 m) the sidewalk tops out at 169 cm while Road_Hollywood is at
366 cm. The road is about 2 m ABOVE its sidewalk, so the player lands on the road and no collision
setting on the sidewalk can change that - the sidewalk is not where the street is. The two surfaces
were generated from different elevation passes and no longer meet at the kerb.

That is site-pipeline data, not placement. The next step is to regenerate road and sidewalks from
one elevation pass so they meet at the kerb, which is one export, one import and one 14 s probe now
that the loop is fast.
2026-11-09 18:19:39 +01:00
zwitschi e50f620aa3 feat(debug): -probes quits after measuring, so a ground check takes 14 s not minutes
The ground measurement was the slowest part of every iteration: the game ran until the shell's
timeout because nothing told it to stop. -probes now logs the top surface, the capsule and the
body's lowest point three seconds after spawn and quits on the next tick. A run went from minutes
to 13.7 s, which is the difference between one experiment per turn and one per five minutes.

Also applied set_complex_collision_as_simple to sidewalks and kerbs, as the terrain already had:
it did not make them block, so that flag is not their problem either.

And a correction that matters: the road is 366 and the sidewalk 380 in the shipped map. The 158 cm
I measured earlier came from the site blend's own Z datum, which sits about 2.2 m below the map's
because the terrain was added after that blend was written. So the float is about 14 cm - the
character stands on the road because the sidewalk does not collide - not the metre it looks like
against the kerb.
2026-11-09 18:15:17 +01:00
zwitschi 1916321761 feat(site): slice the ground into 20 m segments - and the slice hypothesis dies with them
The site exporter now splits sidewalks and kerbs into ~20 m pieces
(SEGMENTED / SEGMENT_M in Art/blender/site/export_site_fbx.py), 56 slices in all, so each has a
bounding box that means something where it stands instead of reporting the highest point on a
279 m slope. They import and stage (63 site meshes in the demo map).

The hypothesis behind it is dead, and that is the useful part: with the invisible sidewalk
stopgap retired, the player now falls to Road_Hollywood at 366 - the slices do not block either.
So the 279 m aspect ratio was never the cause; a 20 m slice of the same mesh fails identically.
The sidewalk geometry itself is what does not collide, and the next place to look is its shape -
an open surface with no volume, inverted winding or zero-thickness triangles - not the import
flags and not the size.

The stopgap retirement stands: the player walks on the road, which is real geometry that does
collide, rather than on an invisible ledge.
2026-11-09 18:13:29 +01:00