test(vehicle): the pawn is the culprit, the meshes are fine

Decisive experiment: the same SM_Vehicle_Body staged as a plain StaticMeshActor 5.5 m
from the pawn renders, while the panels attached to the vehicle pawn do not - with
visible=1, the right mesh name, a 238 cm extent, MarkRenderStateDirty() and
UpdateBounds() all in place. The asset and its material are fine; the pawn's component
hierarchy is what keeps them out of the renderer.

So the car is now staged as nine plain StaticMeshActors (body, glass, front/rear lights,
mirrors, four wheels) alongside the physics pawn: visible, and not yet attached to the
driving pawn. A visible car that does not drive yet beats an invisible one that does.

Two defects found by the tools while chasing this, both fixed: the demo builder staged the
vehicle twice per run (the scene audit caught the duplicate label), and clear() could leave
actors behind, so it now repeats until nothing is removed.
This commit is contained in:
zwitschi committed 2026-11-09 15:42:22 +01:00
1 parent 1cddfbfc68
commit 021aa56121
37 files changed
+92 -56

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+46 -10
View File
@@ -305,8 +305,16 @@ def build():
log(f"could not create {MAP}")
return
removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "RetailTrim_",
"ShotSequencer"))
# Repeat until nothing is left: destroying actors while iterating a snapshot can miss
# some, which is how two Demo_Vehicle_Sedan actors survived into a saved map and the
# scene audit failed on a duplicate label.
removed = 0
for _ in range(4):
pass_removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_",
"RetailTrim_", "ShotSequencer"))
removed += pass_removed
if pass_removed == 0:
break
log(f"cleared {removed} previous demo actors")
# --- lighting: fixed daytime ------------------------------------------
@@ -647,20 +655,48 @@ def build():
unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0))
log("staged: Demo_PlayerStart on the cut north sidewalk, facing south (+Y)")
# The drivable sedan, parked on the carriageway east of the spawn so a review camera
# can frame both. Collision and mass come from PA_Vehicle_Sedan; AGigLineVehicle
# configures the Chaos wheels from the modelled dimensions.
# --- vehicle ---------------------------------------------------------
# The physics pawn and the visible panels are staged as two things on purpose: the
# probe showed the meshes render as plain StaticMeshActors while the same meshes
# attached to the vehicle pawn stay invisible, so the visuals do not depend on the
# pawn until that is solved. A visible car that does not drive yet beats an invisible
# one that does.
vehicle_origin = unreal.Vector(-12950.0, -900.0, 470.0)
vehicle_class = unreal.load_class(None, "/Script/GigLine.GigLineVehicle")
if vehicle_class:
# Spawn ON the road: a downward trace measured the surface at Z 366 cm there, so
# the previous Z 120 spawn buried the car 2.5 m inside the ground - which is why
# it was invisible in the capture while its shadow still fell on the surface.
vehicle = spawn(vehicle_class, unreal.Vector(-12950.0, -900.0, 470.0),
"Demo_Vehicle_Sedan")
vehicle = spawn(vehicle_class, vehicle_origin, "Demo_Vehicle_Sedan")
log(f"staged: Demo_Vehicle_Sedan ({'ok' if vehicle else 'FAILED'})")
else:
log("staged: Demo_Vehicle_Sedan skipped - class not found")
panels = (
("SM_Vehicle_Body", unreal.Vector(0.0, 0.0, -72.0)),
("SM_Vehicle_Glass", unreal.Vector(0.0, 0.0, -72.0)),
("SM_Vehicle_Light_Front", unreal.Vector(0.0, 0.0, -72.0)),
("SM_Vehicle_Light_Rear", unreal.Vector(0.0, 0.0, -72.0)),
("SM_Vehicle_Mirrors", unreal.Vector(0.0, 0.0, -72.0)),
("SM_Vehicle_Wheel", unreal.Vector(139.0, 81.0, -88.0)),
("SM_Vehicle_Wheel", unreal.Vector(139.0, -81.0, -88.0)),
("SM_Vehicle_Wheel", unreal.Vector(-139.0, 81.0, -88.0)),
("SM_Vehicle_Wheel", unreal.Vector(-139.0, -81.0, -88.0)),
)
staged_panels = 0
for index, (mesh_name, offset) in enumerate(panels):
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Vehicle/{mesh_name}")
if not mesh:
log(f"vehicle panel {mesh_name}: MISSING")
continue
actor = spawn(unreal.StaticMeshActor, vehicle_origin + offset,
f"Demo_VehiclePanel_{index:02d}")
if actor:
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
if comp:
comp.set_static_mesh(mesh)
comp.set_mobility(unreal.ComponentMobility.MOVABLE)
staged_panels += 1
log(f"staged: {staged_panels} Demo_VehiclePanel actors (plain static meshes)")
# A lockable LFS .umap checks out read-only, which makes save_map silently
# return False — chmod u+w first or the staged actors never persist.
umap = os.path.join(unreal.Paths.project_dir(), MAP.replace("/Game/", "Content/") + ".umap")