From 370953097745b0e5d841bab3ebf63b664c8b9aa6 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 20:12:19 +0100 Subject: [PATCH 01/13] qa fix (wip): input assets hard-referenced, static site collision, spawn on sidewalk Carries the QA fixes for the walkable demo off main onto an error-fixing branch so a live window can be tested without blocking the clean main. - GigLineCharacter: hard-reference the Enhanced Input assets via ConstructorHelpers so the cooker includes /Game/Input (they were soft-only and pruned from the pak -> no inputs bound). Push the mapping context on a deferred timer after possession. - defaultgame.ini: +DirectoriesToAlwaysCook for /Game/Input, Hollywood, Test. - ue_build_demo_map.py: stage site meshes STATIC with explicit collision (QUERY_AND_PHYSICS, WorldStatic, BlockAll); hero facade STATIC; PlayerStart moved onto the north sidewalk at Y -1200. - DemoMap.umap rebuilt with the above. STATUS: packaged build now logs all input assets ok and grounded=1/Walking, so falling + input-nil are fixed in the package; the user still hit a stuck window (no mouse/keys) in one test, so this remains WIP pending a clean interactive run. --- Game/GigLine/Config/DefaultGame.ini | 9 ++ .../Source/GigLine/GigLineCharacter.cpp | 92 ++++++++++++------- Tools/ue_build_demo_map.py | 28 ++++-- 3 files changed, 84 insertions(+), 45 deletions(-) diff --git a/Game/GigLine/Config/DefaultGame.ini b/Game/GigLine/Config/DefaultGame.ini index 8f9867d..6924d0a 100644 --- a/Game/GigLine/Config/DefaultGame.ini +++ b/Game/GigLine/Config/DefaultGame.ini @@ -7,6 +7,15 @@ CopyrightNotice= BuildConfiguration=PPBC_Development FullRebuild=False UsePakFile=True +; Runtime-loaded content must ship even when nothing statically references it. +; The Enhanced Input assets are only soft-loaded by AGigLineCharacter at +; BeginPlay; without this the cooker prunes them from the pak (observed: the +; cook ReferencedSet had zero /Game/Input entries, so inputs were all nil and +; no keys worked). Always cook the demo map + input. Keep both maps listed so +; EditorStartupMap/GameDefaultMap selection stays flexible. ++DirectoriesToAlwaysCook=/Game/Input ++DirectoriesToAlwaysCook=/Game/Hollywood ++DirectoriesToAlwaysCook=/Game/Test [/Script/EngineSettings.GameMapsSettings] ; Temporary default while the demo map is being built; GameMapsSettings in diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index d294545..ee85116 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -9,6 +9,9 @@ #include "InputAction.h" #include "GameFramework/PlayerController.h" #include "InputMappingContext.h" +#include "Components/CapsuleComponent.h" +#include "Engine/World.h" +#include "TimerManager.h" AGigLineCharacter::AGigLineCharacter() { @@ -39,51 +42,56 @@ AGigLineCharacter::AGigLineCharacter() GetMesh()->SetHiddenInGame(true); GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision); - // Input assets are created by Tools/ue_create_input_assets.py. They are loaded in - // BeginPlay rather than the constructor: the constructor runs during CDO - // construction where asset loads are unreliable, and a missing asset should be - // a loud warning, not a silent no-op. -} + // Hard-reference the Enhanced Input assets with ConstructorHelpers so the + // cooker always includes them and they load during CDO construction. Runtime + // soft-loading them at BeginPlay was fragile: the assets were pruned from the + // pak unless /Game/Input was force-cooked, and a soft path can miss while the + // IoStore registry is still mounting. A constructor FObjectFinder is a hard + // reference (followed by the cooker) and loads deterministically. + // Assets are created by Tools/ue_create_input_assets.py. + static ConstructorHelpers::FObjectFinder CtxFinder( + TEXT("/Game/Input/IMC_Default.IMC_Default")); + static ConstructorHelpers::FObjectFinder MoveF( + TEXT("/Game/Input/IA_Move.IA_Move")); + static ConstructorHelpers::FObjectFinder StrafeF( + TEXT("/Game/Input/IA_Strafe.IA_Strafe")); + static ConstructorHelpers::FObjectFinder BackF( + TEXT("/Game/Input/IA_MoveBack.IA_MoveBack")); + static ConstructorHelpers::FObjectFinder LeftF( + TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft")); + static ConstructorHelpers::FObjectFinder LookF( + TEXT("/Game/Input/IA_Look.IA_Look")); + static ConstructorHelpers::FObjectFinder JumpF( + TEXT("/Game/Input/IA_Jump.IA_Jump")); + static ConstructorHelpers::FObjectFinder ResetF( + TEXT("/Game/Input/IA_Reset.IA_Reset")); + static ConstructorHelpers::FObjectFinder PauseF( + TEXT("/Game/Input/IA_Pause.IA_Pause")); -void AGigLineCharacter::LoadInputAssets() -{ - auto LoadSoft = [](const TCHAR* Path) -> TObjectPtr - { - return LoadObject(nullptr, Path); - }; - MappingContext = Cast(LoadSoft(TEXT("/Game/Input/IMC_Default.IMC_Default"))); - MoveAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Move.IA_Move"))); - StrafeAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Strafe.IA_Strafe"))); - MoveBackAction = Cast(LoadSoft(TEXT("/Game/Input/IA_MoveBack.IA_MoveBack"))); - StrafeLeftAction = Cast(LoadSoft(TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft"))); - LookAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Look.IA_Look"))); - JumpAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Jump.IA_Jump"))); - ResetAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Reset.IA_Reset"))); - PauseAction = Cast(LoadSoft(TEXT("/Game/Input/IA_Pause.IA_Pause"))); + MappingContext = CtxFinder.Object; + MoveAction = MoveF.Object; + StrafeAction = StrafeF.Object; + MoveBackAction = BackF.Object; + StrafeLeftAction = LeftF.Object; + LookAction = LookF.Object; + JumpAction = JumpF.Object; + ResetAction = ResetF.Object; + PauseAction = PauseF.Object; if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction || !LookAction) { UE_LOG(LogTemp, Warning, - TEXT("GigLine: input assets missing (context=%s move=%s look=%s). ") - TEXT("Run Tools/ue_create_input_assets.py to build /Game/Input."), - MappingContext ? TEXT("ok") : TEXT("missing"), - (MoveAction && MoveBackAction && StrafeAction && StrafeLeftAction) ? TEXT("ok") - : TEXT("missing"), - LookAction ? TEXT("ok") : TEXT("missing")); + TEXT("GigLine: input assets failed to load at construction. ") + TEXT("Run Tools/ue_create_input_assets.py to build /Game/Input.")); } } -void AGigLineCharacter::BeginPlay() +void AGigLineCharacter::LoadInputAssets() { - Super::BeginPlay(); - - LoadInputAssets(); - - StartLocation = GetActorLocation(); - StartRotation = GetActorRotation(); - - // Push the mapping context for this local player. + // The assets are hard-referenced and loaded in the constructor; this deferred + // call pushes the now-valid mapping context to the local player, after the + // pawn has been possessed and its PlayerController exists. if (MappingContext) { if (const APlayerController* PC = Cast(GetController())) @@ -97,6 +105,20 @@ void AGigLineCharacter::BeginPlay() } } +void AGigLineCharacter::BeginPlay() +{ + Super::BeginPlay(); + + StartLocation = GetActorLocation(); + StartRotation = GetActorRotation(); + + // Input assets are hard-referenced in the constructor. Defer the mapping + // context push to just after spawn, when the PlayerController exists and a + // packaged game has finished mounting its pak IoStore. + FTimerHandle InputHandle; + GetWorldTimerManager().SetTimer(InputHandle, this, &AGigLineCharacter::LoadInputAssets, 1.0f, false); +} + void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 5a9b9d8..0bda350 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -146,7 +146,7 @@ def build(): log(f"could not create {MAP}") return - removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_")) + removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "ShotSequencer")) log(f"cleared {removed} previous demo actors") # --- lighting: fixed daytime ------------------------------------------ @@ -256,7 +256,7 @@ def build(): actor = spawn(unreal.StaticMeshActor, unreal.Vector(-13336.0, 1307.0, 0.0), "Hero_Facade") if actor: actor.static_mesh_component.set_static_mesh(hero) - actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) + actor.static_mesh_component.set_mobility(unreal.ComponentMobility.STATIC) log("staged: Hero_Facade at the building line") else: log("MISSING: SM_HeroFacade — run Tools/ue_hero_facade_import.py") @@ -303,11 +303,18 @@ def build(): continue actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"Demo_{name}") if actor: - actor.static_mesh_component.set_static_mesh(mesh) - actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE) + comp = actor.static_mesh_component + comp.set_static_mesh(mesh) + # Immutable level geometry must be STATIC to reliably expose its + # complex-as-simple collision body to CharacterMovement; staging + # these MOVABLE let the player fall through the world. + comp.set_mobility(unreal.ComponentMobility.STATIC) + comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS) + comp.set_collision_object_type(unreal.CollisionChannel.ECC_WORLD_STATIC) + comp.set_collision_profile_name("BlockAll") mat = built.get(mat_name) if mat: - actor.static_mesh_component.set_material(0, mat) + comp.set_material(0, mat) imported += 1 log(f"staged {imported} site meshes with explicit surface materials") @@ -362,11 +369,12 @@ def build(): # --- player start: north sidewalk, facing the hero frontage ------------- # Signs were measured from the imported meshes rather than assumed: the FBX # preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH - # sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. The - # facade stands at Y -1450. Stand on the north sidewalk and look north (-Y). - # North sidewalk (Y -1912..-1053), facing south (+Y) across the road at the - # hero on the south side. Yaw 90 looks along +Y. - spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 120.0), "Demo_PlayerStart", + # sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. + # The hero facade is on the SOUTH side at Y +1307. Stand on the north + # sidewalk and face south (+Y, yaw 90) across the road at it. + # Z 200 is clear air so the capsule settles onto the pavement instead of + # spawning embedded or below grade (the fall-through QA finding). + spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 200.0), "Demo_PlayerStart", unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0)) log("staged: Demo_PlayerStart on the north sidewalk facing south (+Y)") -- 2.47.3 From c09352b4af2cc98c74c2ae5a34f1627fb692b642 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 20:20:41 +0100 Subject: [PATCH 02/13] fix: default map is the runnable DemoMap, not the engine template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A plain packaged launch (./GigLine.sh with no -map) opened /Engine/Maps/Templates/OpenWorld — an empty engine template with nothing to do, which presented as a 'stuck' window with no working mouse/keys. The GameDefaultMap setting lived in DefaultGame.ini and wasn't taking effect. Move GameMapsSettings (GameDefaultMap/EditorStartupMap/ServerDefaultMap/ TransitionMap = /Game/Hollywood/DemoMap) into DefaultEngine.ini alongside the GlobalDefaultGameMode, and drop the stale ReviewMap default from DefaultGame.ini. Verified: plain ./GigLine.sh now loads /Game/Hollywood/DemoMap, Enhanced Input initializes, Vulkan window opens. --- Game/GigLine/Config/DefaultEngine.ini | 6 ++++++ Game/GigLine/Config/DefaultGame.ini | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Game/GigLine/Config/DefaultEngine.ini b/Game/GigLine/Config/DefaultEngine.ini index 80015db..f160c35 100644 --- a/Game/GigLine/Config/DefaultEngine.ini +++ b/Game/GigLine/Config/DefaultEngine.ini @@ -1,5 +1,11 @@ [/Script/EngineSettings.GameMapsSettings] GlobalDefaultGameMode="/Script/GigLine.GigLineGameModeBase" +; The runnable map is the walkable demo, so a plain launch opens it (not the +; engine template nor the review staging scene). +GameDefaultMap=/Game/Hollywood/DemoMap +EditorStartupMap=/Game/Hollywood/DemoMap +ServerDefaultMap=/Game/Hollywood/DemoMap +TransitionMap=/Game/Hollywood/DemoMap [/Script/Engine.RendererSettings] ; Dynamic-only lighting. With static lighting allowed, the stationary sun and diff --git a/Game/GigLine/Config/DefaultGame.ini b/Game/GigLine/Config/DefaultGame.ini index 6924d0a..33938f0 100644 --- a/Game/GigLine/Config/DefaultGame.ini +++ b/Game/GigLine/Config/DefaultGame.ini @@ -17,8 +17,4 @@ UsePakFile=True +DirectoriesToAlwaysCook=/Game/Hollywood +DirectoriesToAlwaysCook=/Game/Test -[/Script/EngineSettings.GameMapsSettings] -; Temporary default while the demo map is being built; GameMapsSettings in -; DefaultEngine.ini still names the game mode only. -GameDefaultMap=/Game/Test/ReviewMap -EditorStartupMap=/Game/Test/ReviewMap + -- 2.47.3 From f8c33ae697cd7f833bc3d420890245cbe52cff33 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 20:20:45 +0100 Subject: [PATCH 03/13] memory: document walkable demo QA root causes (default map, input cook, collision) --- MEMORY.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/MEMORY.md b/MEMORY.md index 61c7a65..23c2bb5 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -199,3 +199,24 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - The two north-side buildings (TCL, Tussaud's) were similarly on the **south** side: `export_north_buildings.py` mirrored Y, which put them where the frontage belongs. - Fix: hero at X −13336 / Y +1307 (the frontage gap between 6902 and 6838); frontage and north buildings exported **without** a Y mirror (the FBX preset already maps Blender −Y south to UE +Y south). - Lesson: **place a building only against a neighbour or the street mesh, never in isolation.** A single object in an empty map hides any number of placement errors; adding the adjacent frontage immediately exposed this one. +## 2026-11-07 — walkable demo QA bugs (fix branch qa-input-collision-fix-0.1.0) + +- **"Stuck" window = wrong default map.** A plain `./GigLine.sh` opened + `/Engine/Maps/Templates/OpenWorld` (empty engine template) instead of the demo, + so nothing was interactive. `GameDefaultMap` was in `DefaultGame.ini` and not + applied; it must live in `DefaultEngine.ini[/Script/EngineSettings.GameMapsSettings]` + with `GlobalDefaultGameMode`. Verified: plain launch now loads + `/Game/Hollywood/DemoMap`. Evidence: `LogLoad: LoadMap: /Engine/Maps/Templates/OpenWorld`. +- **Input assets were pruned from the pak.** The Enhanced Input assets under + `/Game/Input` were only soft-referenced at runtime, so the cooker excluded them + (cook `ReferencedSet.txt` had zero `/Game/Input` lines -> all inputs nil at + runtime). Fix: hard-reference via `ConstructorHelpers::FObjectFinder` in the + character ctor (cooker follows hard refs) + `+DirectoriesToAlwaysCook`. Packaged + log then showed `ctx=ok move=ok ... jump=ok`. +- **Character fell through** the site: site meshes staged MOVABLE + PlayerStart far + past the north sidewalk. Fix: STATIC mobility + explicit collision + (QUERY_AND_PHYSICS/WorldStatic/BlockAll) and PlayerStart at Y -1200 on the + sidewalk. Packaged run: `grounded=1 movement=Walking`. +- Diagnose packaged runtime via the engine log + (`.../Linux/GigLine/Saved/Logs/GigLine.log`); a headless `-nullrhi` commandlet + cannot validate physics traces (even a spawned cube returns NO HIT there). -- 2.47.3 From 1cc487b9164eecd218d73fa20f381ccbd80dba6d Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 20:20:54 +0100 Subject: [PATCH 04/13] docs: note plain launch must open DemoMap (stuck-scene trap) --- Docs/build.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Docs/build.md b/Docs/build.md index 2570f50..acad444 100644 --- a/Docs/build.md +++ b/Docs/build.md @@ -46,9 +46,15 @@ Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \ -archive -archivedirectory="/run/media/system/DATA/Builds/GigLine" \ -unattended # Expected: "BUILD SUCCESSFUL" (~1 min for the blank project; archive ~1.1 GiB) -# Launch test: /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh +# Launch test (plain, no -map): /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh ``` +**Launch test must open the DemoMap.** A plain `./GigLine.sh` should load +`/Game/Hollywood/DemoMap` (set via `DefaultEngine.ini` `GameMapsSettings`). If it +instead opens `/Engine/Maps/Templates/OpenWorld`, the game shows an empty +non-interactive "stuck" scene — check that `GameDefaultMap` is in +`DefaultEngine.ini`, not `DefaultGame.ini`. Confirm the loaded map in the engine +log: `.../Linux/GigLine/Saved/Logs/GigLine.log` (`LoadMap: /Game/Hollywood/DemoMap`). The archive directory is deliberately **outside** the repository; never archive into the checkout. ## Headless content checks -- 2.47.3 From eb199ccce6222461b71349cfc93c1c997abd07a9 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 21:09:15 +0100 Subject: [PATCH 05/13] qa wip: collide site meshes, spawn cap above grade (still investigating fall-through) - ue_import_site.py: auto_generate_collision=True so FBX static meshes get real collision geometry (they imported with none -> complex-as-simple had nothing to use, player fell through). - ue_build_demo_map.py: PlayerStart cap raised to Z300 so the capsule feet start above the ~Z99-138 terrain surface there (an embedded spawn point is rejected -> 'NO PLAYERSTART with positive rating' -> origin fallback). - Re-imported site meshes + rebuilt DemoMap with the above. KNOWN-UNRESOLVED: packaged plain launch still spawns the capsule around Z-123 and falls; downward trace finds no geometry. Map assets are all present in the pak and referenced; runtime collision behavior needs a visual/interactive check (headless commandlet traces are unreliable). Investigate on this branch; do not merge to main. --- .../Content/Hollywood/Site/Crossing_Markings.uasset | 2 +- Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset | 2 +- Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset | 2 +- .../Content/Hollywood/Site/Road_Hollywood.uasset | 2 +- .../Content/Hollywood/Site/Sidewalk_North.uasset | 2 +- .../Content/Hollywood/Site/Sidewalk_South.uasset | 2 +- .../GigLine/Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- Tools/ue_build_demo_map.py | 11 +++++++---- Tools/ue_import_site.py | 6 ++++++ 9 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset b/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset index 4c3e2c1..78b30f8 100644 --- a/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70c729bb152a8bbd7cfe2f457e0d3fa1b1bac455d696bb27d23644aecff7cecd +oid sha256:fbe6957a64e5066e8cb39d6cc0dfac32e8b8467f1ce96f79efbfc4f0164caffc size 75501 diff --git a/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset b/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset index 88cb645..21d946d 100644 --- a/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7893216176a7977d4784234bf10e6b49a219ab31dd61497f8db7958c5a04b7ce +oid sha256:0a0290a913eb20cf7a4cb0babf9c85873fa27a8702bd668e2f5ba7183fe1ba32 size 83550 diff --git a/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset b/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset index 7f5ec1b..78fe5ab 100644 --- a/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd5493759b160992ebec5073e59d67a6ea8ceeb1fbb35b0624223668934cebc3 +oid sha256:f8a457f7846e3be2baec0e7c67a1483b631a000b62ea84fab512d17e1d5dcf9b size 83797 diff --git a/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset b/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset index 908fab3..d1752f5 100644 --- a/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b2c133f3dc163f937cc7f73de9ee35a31659451e43171abdaa1b1fc5b357e1a +oid sha256:2d1b18b0fd9a8cf03bdd4167adc7c522ba317534e5cb218e61dc35d800b93846 size 309810 diff --git a/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset b/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset index 1416015..22407dd 100644 --- a/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3fa880ce24eff71415cf8039b9e2347181087c2fa42ac217b022a55e1cc68a8 +oid sha256:8b2ae64e3b75e91a020f8064a6dc3ecd338c1c221e22913207e520d41bbed1d7 size 150676 diff --git a/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset b/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset index 9b7eae9..bfcd3dc 100644 --- a/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf71bb2cd8feebb44a102f9a4f44179a5d26d7fb125cf5fbec31d5084acbd9c4 +oid sha256:a0d59d5e94bf4d80f26f4642dfccd3e81f95de31c21200f75889483be055b660 size 164347 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 7d7ac7d..4d876fc 100644 --- a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa5407698175d4ca2a8e0ee7e3bdde23654337445260c9b314a62667d8edcdc1 +oid sha256:c69a8740d12b055236a1e178fee029affb2c15a76e76a57456545469319828ee size 335528 diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 0bda350..2c897b8 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -372,11 +372,14 @@ def build(): # sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. # The hero facade is on the SOUTH side at Y +1307. Stand on the north # sidewalk and face south (+Y, yaw 90) across the road at it. - # Z 200 is clear air so the capsule settles onto the pavement instead of - # spawning embedded or below grade (the fall-through QA finding). - spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 200.0), "Demo_PlayerStart", + # Z: the terrain here is ~Z 99-138. The capsule half-height is 87.5 cm, so + # feet must start ABOVE the surface or the point is embedded and UE rejects + # the PlayerStart ("NO PLAYERSTART with positive rating"), dropping the pawn + # at the world origin to fall through. Cap feet ~80 cm above grade: centre + # ~138+80+87 = 305, so Z 300 lets it drop cleanly and gravity settles it. + spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 300.0), "Demo_PlayerStart", unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0)) - log("staged: Demo_PlayerStart on the north sidewalk facing south (+Y)") + log("staged: Demo_PlayerStart on the north sidewalk (cap above grade), facing south (+Y)") # 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. diff --git a/Tools/ue_import_site.py b/Tools/ue_import_site.py index bf50a86..48f1f94 100644 --- a/Tools/ue_import_site.py +++ b/Tools/ue_import_site.py @@ -50,6 +50,12 @@ def import_fbx(path, name, collision="complex"): smd.convert_scene_unit = False smd.force_front_x_axis = False smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS + # Generate collision at import. A default FBX static-mesh import creates NO + # collision primitives, so CTF_USE_COMPLEX_AS_SIMPLE later has nothing to use + # and the player falls through the walkable surfaces (observed at runtime: + # ground trace under the spawn hits nothing). auto_generate_collision bakes + # collision geometry into the mesh so the walkable plane actually blocks. + smd.auto_generate_collision = True task.options = ui unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) -- 2.47.3 From ea16818e2adf064a6db6a77b234ed6f7820bf641 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 21:18:15 +0100 Subject: [PATCH 06/13] qa: walkable meshes use SIMPLE_AND_COMPLEX collision (WIP on fall-through) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ue_import_site.py: switch walkable surfaces (terrain, road, sidewalks) from CTF_USE_COMPLEX_AS_SIMPLE to CTF_USE_SIMPLE_AND_COMPLEX so the capsule blocks against the auto-generated simple collision as well as triangles. - Re-imported site meshes, rebuilt DemoMap with the PlayerStart cap at Z300. Unresolved: packaged plain launch still falls through at spawn in the last verified run (capsule Z-123 -> -1960, no ground trace hit). User confirms the site geometry RENDERS (visible in the distance) but the player is not standing on it. Needs an in-editor/in-window collision verification — headless traces are unreliable. Continue on this branch; do not merge to main. --- .../Content/Hollywood/Site/Crossing_Markings.uasset | 2 +- Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset | 2 +- Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset | 2 +- Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset | 4 ++-- Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset | 4 ++-- Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset | 4 ++-- Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset | 4 ++-- Tools/ue_import_site.py | 8 ++++++-- 8 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset b/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset index 78b30f8..056b009 100644 --- a/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Crossing_Markings.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbe6957a64e5066e8cb39d6cc0dfac32e8b8467f1ce96f79efbfc4f0164caffc +oid sha256:ebac8b2f24eb5a9694ae773afd066c8119b35e508b9768443a115591943ef3d0 size 75501 diff --git a/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset b/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset index 21d946d..a565fe5 100644 --- a/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Kerb_North.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a0290a913eb20cf7a4cb0babf9c85873fa27a8702bd668e2f5ba7183fe1ba32 +oid sha256:afc0287a2021ac3c8249a6071a34c1b7d20e86329071bb0ec47971f675703f13 size 83550 diff --git a/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset b/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset index 78fe5ab..a5257be 100644 --- a/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Kerb_South.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8a457f7846e3be2baec0e7c67a1483b631a000b62ea84fab512d17e1d5dcf9b +oid sha256:244dc615c604332bc8046c61305264997f63d03bc6ed33281e22436a8fb17127 size 83797 diff --git a/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset b/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset index d1752f5..affcbe6 100644 --- a/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Road_Hollywood.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d1b18b0fd9a8cf03bdd4167adc7c522ba317534e5cb218e61dc35d800b93846 -size 309810 +oid sha256:484203d50293ca93dcdfceb275c24587a40f70892698b6f6950b600358522fa3 +size 309812 diff --git a/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset b/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset index 22407dd..c335f1a 100644 --- a/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Sidewalk_North.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b2ae64e3b75e91a020f8064a6dc3ecd338c1c221e22913207e520d41bbed1d7 -size 150676 +oid sha256:53bc30b5df51eabe14e5a31e33fd5b0d641160289c92dd3717df5af8b9bd43e2 +size 150678 diff --git a/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset b/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset index bfcd3dc..11756b3 100644 --- a/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Sidewalk_South.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0d59d5e94bf4d80f26f4642dfccd3e81f95de31c21200f75889483be055b660 -size 164347 +oid sha256:a1f11f8a3448a0e26a530d928d5e613c673f3b899ee1922ef7d9eb7b3c428c06 +size 164349 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 4d876fc..4f38add 100644 --- a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c69a8740d12b055236a1e178fee029affb2c15a76e76a57456545469319828ee -size 335528 +oid sha256:7c01d12a342abd31999ada2f3cc98defdff53bb831ed8f39dd6523654b2bc8d4 +size 335530 diff --git a/Tools/ue_import_site.py b/Tools/ue_import_site.py index 48f1f94..67fee09 100644 --- a/Tools/ue_import_site.py +++ b/Tools/ue_import_site.py @@ -77,8 +77,12 @@ def import_fbx(path, name, collision="complex"): log(f"{name:18} import ok size=({mx.x - mn.x:.0f}, {mx.y - mn.y:.0f}, {mx.z - mn.z:.0f}) cm " f"min_z={mn.z:.1f} collision=none") return - trace = (unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE - if collision == "complex" else unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX) + # Walkable surfaces block with SIMPLE + COMPLEX collision so the player's + # capsule always has ground: CFG_USE_SIMPLE_AND_COMPLEX uses the auto- + # generated simple collision AND the mesh triangles. (Complex-as-simple + # alone only worked if the mesh carried complex collision, which a default + # FBX static-mesh import does not.) + trace = unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX try: body = mesh.get_editor_property("body_setup") body.set_editor_property("collision_trace_flag", trace) -- 2.47.3 From 2199a546bdba0f0dbed67d82e303e9cd76b5fb83 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 7 Nov 2026 22:14:29 +0100 Subject: [PATCH 07/13] qa wip: spawn in front of facade + input/sim diagnostics - PlayerStart moved to (-10722,-2400,800) clear air, facing the facade. - Force game input mode + hide cursor at BeginPlay. - Diagnostics: every-second world/Z/gravity/fall log; one HighResShot at 1.5s. Verified (packaged, engine log): controller connects to pawn input=yes, ctx pushed, actions bound, world ticks, gravity -980, but capsule stays Z=909 fall=0 forever and no input fires. Static airborne pawn + no fall + no input = pawn simulated without an active autonomous local player. Recorded for next debug; do not merge to main. --- Game/GigLine/Content/Input/IA_Move.uasset | 4 +- Game/GigLine/Content/Input/IA_MoveBack.uasset | 4 +- Game/GigLine/Content/Input/IA_Strafe.uasset | 4 +- .../Content/Input/IA_StrafeLeft.uasset | 4 +- .../Source/GigLine/GigLineCharacter.cpp | 79 ++++++++++++++++--- .../Source/GigLine/GigLineGameModeBase.cpp | 10 +++ .../Source/GigLine/GigLineGameModeBase.h | 4 + Tools/ue_create_input_assets.py | 12 ++- 8 files changed, 97 insertions(+), 24 deletions(-) diff --git a/Game/GigLine/Content/Input/IA_Move.uasset b/Game/GigLine/Content/Input/IA_Move.uasset index 132b853..99ea36d 100644 --- a/Game/GigLine/Content/Input/IA_Move.uasset +++ b/Game/GigLine/Content/Input/IA_Move.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4278563b2653917e011eac249c4400c7e0874a59374afede5b7d0ca665a81916 -size 1306 +oid sha256:346f83e78be0e76f39c4cc0a4aa39fa783ed77f070001510f094fba3d79e45a4 +size 1110 diff --git a/Game/GigLine/Content/Input/IA_MoveBack.uasset b/Game/GigLine/Content/Input/IA_MoveBack.uasset index 26c4977..6c3fe87 100644 --- a/Game/GigLine/Content/Input/IA_MoveBack.uasset +++ b/Game/GigLine/Content/Input/IA_MoveBack.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88c9f7881e8a91c11b60f84af3538e74fc5e94c2731211b40b24159d7b42f137 -size 1322 +oid sha256:0af8585fb7f572e80625915845d4efc940629586431f95d04b8e695661720f43 +size 1126 diff --git a/Game/GigLine/Content/Input/IA_Strafe.uasset b/Game/GigLine/Content/Input/IA_Strafe.uasset index 1f88636..4b16d43 100644 --- a/Game/GigLine/Content/Input/IA_Strafe.uasset +++ b/Game/GigLine/Content/Input/IA_Strafe.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a30d908d7c055af0e41d0d6900c19bacd218387db36bab281895a8802936c992 -size 1314 +oid sha256:79c1877d7f84218179ae68b6ceb0bd9ab68623e3ecf617f733c65f9364484362 +size 1118 diff --git a/Game/GigLine/Content/Input/IA_StrafeLeft.uasset b/Game/GigLine/Content/Input/IA_StrafeLeft.uasset index 83f7a34..0c6fd72 100644 --- a/Game/GigLine/Content/Input/IA_StrafeLeft.uasset +++ b/Game/GigLine/Content/Input/IA_StrafeLeft.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aeaad125209e4c80921ac4e674a0aeb0b12bf9240b4d0110a74583bb04eb7810 -size 1330 +oid sha256:23b0fdb4a5d3cef749b89acb50c45fe7e6a824b5d29db3de6163f81e98d53dda +size 1134 diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index ee85116..13a768f 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -7,9 +7,7 @@ #include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/PlayerController.h" #include "InputAction.h" -#include "GameFramework/PlayerController.h" #include "InputMappingContext.h" -#include "Components/CapsuleComponent.h" #include "Engine/World.h" #include "TimerManager.h" @@ -89,9 +87,9 @@ AGigLineCharacter::AGigLineCharacter() void AGigLineCharacter::LoadInputAssets() { - // The assets are hard-referenced and loaded in the constructor; this deferred - // call pushes the now-valid mapping context to the local player, after the - // pawn has been possessed and its PlayerController exists. + // The assets are hard-referenced and loaded in the constructor. This pushes + // the now-valid mapping context to the local player's subsystem; called from + // SetupPlayerInputComponent at possession, when the controller exists. if (MappingContext) { if (const APlayerController* PC = Cast(GetController())) @@ -100,7 +98,17 @@ void AGigLineCharacter::LoadInputAssets() ULocalPlayer::GetSubsystem(PC->GetLocalPlayer())) { Subsystem->AddMappingContext(MappingContext, 0); + UE_LOG(LogTemp, Log, TEXT("GigLine IN: pushed mapping context %s"), + *MappingContext->GetName()); } + else + { + UE_LOG(LogTemp, Log, TEXT("GigLine IN: no enhanced input subsystem on local player")); + } + } + else + { + UE_LOG(LogTemp, Log, TEXT("GigLine IN: no PlayerController at LoadInputAssets")); } } } @@ -112,11 +120,39 @@ void AGigLineCharacter::BeginPlay() StartLocation = GetActorLocation(); StartRotation = GetActorRotation(); - // Input assets are hard-referenced in the constructor. Defer the mapping - // context push to just after spawn, when the PlayerController exists and a - // packaged game has finished mounting its pak IoStore. - FTimerHandle InputHandle; - GetWorldTimerManager().SetTimer(InputHandle, this, &AGigLineCharacter::LoadInputAssets, 1.0f, false); + // Force game input mode + focus + mouse capture so keyboard/mouse reach the + // pawn regardless of window focus quirks. + if (APlayerController* PC = Cast(GetController())) + { + PC->SetInputMode(FInputModeGameOnly()); + PC->bShowMouseCursor = false; + } + + // TEMP QA: one screenshot at +1.5s so the actual frame is reviewable. + FTimerHandle ShotHandle; + GetWorldTimerManager().SetTimer(ShotHandle, [this]() + { + if (APlayerController* PC = Cast(GetController())) + { + PC->ConsoleCommand(TEXT("HighResShot 1280 720")); + } + }, 1.5f, false); + + // TEMP QA: every second log world time + capsule Z + gravity + fall state so + // we can see whether the world is simulating the pawn at all (symptom: + // "spawns above ground but never falls, no input"). + FTimerHandle SimHandle; + GetWorldTimerManager().SetTimer(SimHandle, [this]() + { + UCharacterMovementComponent* M = GetCharacterMovement(); + UE_LOG(LogTemp, Log, + TEXT("GigLine SIM: worldTime=%.2f Z=%.0f grav=%.1f fall=%d pawn=%s"), + GetWorld() ? GetWorld()->GetTimeSeconds() : -1.f, + GetActorLocation().Z, + M ? M->GetGravityZ() : 0.f, + (M && M->IsFalling()) ? 1 : 0, + *GetName()); + }, 1.0f, true); } void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) @@ -130,6 +166,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo return; } + // Push the mapping context for this controller's local player so the bound + // actions receive input (runs at possession, when the controller exists). + LoadInputAssets(); + // One action per direction keeps the sign explicit: W and S differ only by // which handler runs, not by a modifier in the asset. if (MoveAction) @@ -165,6 +205,12 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump); Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &AGigLineCharacter::StopJump); } + UE_LOG(LogTemp, Log, TEXT("GigLine IN: bound %d actions; ctx=%s move=%s look=%s jmp=%s"), + (Input ? 1 : 0), + MappingContext ? TEXT("ok") : TEXT("nil"), + MoveAction ? TEXT("ok") : TEXT("nil"), + LookAction ? TEXT("ok") : TEXT("nil"), + JumpAction ? TEXT("ok") : TEXT("nil")); } namespace @@ -191,6 +237,8 @@ namespace void AGigLineCharacter::MoveForward(const FInputActionValue& Value) { + UE_LOG(LogTemp, Log, TEXT("GigLine IN: MoveForward fired, axis=%.3f type=%d"), + FMath::Abs(SignedAxis(Value, false)), (int)Value.GetValueType()); ApplyMove(FMath::Abs(SignedAxis(Value, false)), 0.0f); } @@ -212,11 +260,16 @@ void AGigLineCharacter::MoveLeft(const FInputActionValue& Value) void AGigLineCharacter::ApplyMove(float Forward, float Strafe) { const AController* Controller = GetController(); - if (!Controller || (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe))) + if (!Controller) + { + UE_LOG(LogTemp, Log, TEXT("GigLine IN: ApplyMove called but NO controller (fwd %.2f strafe %.2f)"), + Forward, Strafe); + return; + } + if (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe)) { return; } - // Forward/right on the controller yaw keeps the capsule upright on slopes. const FRotator YawRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f); const FVector ForwardDir = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); @@ -224,6 +277,8 @@ void AGigLineCharacter::ApplyMove(float Forward, float Strafe) AddMovementInput(ForwardDir, Forward); AddMovementInput(RightDir, Strafe); + UE_LOG(LogTemp, Log, TEXT("GigLine IN: ApplyMove fwd %.2f strafe %.2f controller=%s yaw=%.0f"), + Forward, Strafe, *Controller->GetName(), Controller->GetControlRotation().Yaw); } void AGigLineCharacter::Look(const FInputActionValue& Value) diff --git a/Game/GigLine/Source/GigLine/GigLineGameModeBase.cpp b/Game/GigLine/Source/GigLine/GigLineGameModeBase.cpp index 712292d..dd6bd6f 100644 --- a/Game/GigLine/Source/GigLine/GigLineGameModeBase.cpp +++ b/Game/GigLine/Source/GigLine/GigLineGameModeBase.cpp @@ -1,5 +1,6 @@ #include "GigLineGameModeBase.h" +#include "GameFramework/PlayerController.h" #include "GigLineCharacter.h" #include "GigLineHUD.h" @@ -9,3 +10,12 @@ AGigLineGameModeBase::AGigLineGameModeBase() // ODbL attribution must be visible in the running demo, not only in docs. HUDClass = AGigLineHUD::StaticClass(); } + +void AGigLineGameModeBase::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) +{ + Super::HandleStartingNewPlayer_Implementation(NewPlayer); + UE_LOG(LogTemp, Log, TEXT("GigLine GM: player %s connected; pawn=%s input=%s"), + NewPlayer ? *NewPlayer->GetName() : TEXT("null"), + NewPlayer && NewPlayer->GetPawn() ? *NewPlayer->GetPawn()->GetName() : TEXT("NONE"), + NewPlayer && NewPlayer->PlayerInput ? TEXT("yes") : TEXT("no")); +} diff --git a/Game/GigLine/Source/GigLine/GigLineGameModeBase.h b/Game/GigLine/Source/GigLine/GigLineGameModeBase.h index 23c7f31..598b210 100644 --- a/Game/GigLine/Source/GigLine/GigLineGameModeBase.h +++ b/Game/GigLine/Source/GigLine/GigLineGameModeBase.h @@ -18,4 +18,8 @@ class GIGLINE_API AGigLineGameModeBase : public AGameModeBase public: AGigLineGameModeBase(); + +protected: + /** Logs whether a local player connected to a pawn with input (QA). */ + virtual void HandleStartingNewPlayer_Implementation(class APlayerController* NewPlayer) override; }; diff --git a/Tools/ue_create_input_assets.py b/Tools/ue_create_input_assets.py index 11180ae..ff1a9d5 100644 --- a/Tools/ue_create_input_assets.py +++ b/Tools/ue_create_input_assets.py @@ -85,7 +85,11 @@ def build(): # component and D/A carry strafe, combined by the character. A 2D value would # need per-key axis modifiers, and modifiers do not survive the save path here. ia_move = fresh("IA_Move", unreal.InputAction, unreal.InputAction_Factory) - ia_move.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) + # Boolean (pressed = 1), not AXIS1D: the C++ handler reads Get() and + # moves at full walk speed. An axis action from a keyboard key can deliver 0 + # depending on the trigger, which showed up as "character stands, W does not + # move" in QA. WASD are four separate per-direction actions below. + ia_move.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN) save(ia_move) ia_look = fresh("IA_Look", unreal.InputAction, unreal.InputAction_Factory) @@ -107,15 +111,15 @@ def build(): # Two actions feed the one-dimensional move: IA_Move for forward/back, and a # second action for strafe, both reporting on the value's single component. ia_strafe = fresh("IA_Strafe", unreal.InputAction, unreal.InputAction_Factory) - ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) + ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN) save(ia_strafe) ia_back = fresh("IA_MoveBack", unreal.InputAction, unreal.InputAction_Factory) - ia_back.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) + ia_back.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN) save(ia_back) ia_left = fresh("IA_StrafeLeft", unreal.InputAction, unreal.InputAction_Factory) - ia_left.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) + ia_left.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN) save(ia_left) # Reset and pause are boolean: one press, no accumulator. -- 2.47.3 From 2bab0fb0cd69e69f2591160f46242b00924325f9 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 10:26:19 +0100 Subject: [PATCH 08/13] qa wip: SIM diagnostics + street spawn (pre-rebase snapshot) --- Game/GigLine/Source/GigLine/GigLineCharacter.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index 13a768f..a7fca8d 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -146,12 +146,15 @@ void AGigLineCharacter::BeginPlay() { UCharacterMovementComponent* M = GetCharacterMovement(); UE_LOG(LogTemp, Log, - TEXT("GigLine SIM: worldTime=%.2f Z=%.0f grav=%.1f fall=%d pawn=%s"), + TEXT("GigLine SIM: t=%.2f Z=%.0f grav=%.1f fall=%d local=%d role=%d mode=%d ctrl=%s"), GetWorld() ? GetWorld()->GetTimeSeconds() : -1.f, GetActorLocation().Z, M ? M->GetGravityZ() : 0.f, (M && M->IsFalling()) ? 1 : 0, - *GetName()); + IsLocallyControlled() ? 1 : 0, + (int)GetLocalRole(), + M ? (int)M->MovementMode : -1, + GetController() ? *GetController()->GetName() : TEXT("NONE")); }, 1.0f, true); } -- 2.47.3 From 2bb1e74814a2115d94116cb052202cdac55db042 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 11:30:41 +0100 Subject: [PATCH 09/13] fix(collision): terrain bbox proxy buried the whole street The player never fell through - it stood on an invisible 8 m box. The imported Terrain_DEM carried only a bounding-box simple collision proxy, so every downward trace in the block hit Demo_Terrain_DEM at Z 819 (the mesh bbox max) and the road/sidewalk underneath were unreachable. The view from up there looked plausible, which is why it read as working. - add UGigLineAssetTools (C++): set complex-as-simple collision on a static mesh; UE 5.8 exposes ECollisionTraceFlag, not bUseComplexAsSimpleCollision, and Python cannot set it at all. - builder forces + saves the terrain asset as complex-as-simple (the flag lives on the asset, so saving only the .umap loses it). - spawn (-13336, -1150, 600): inside the carved corridor; lands on the road at Z 456 (feet 368 on road top 366). - HighResShot 1280x720, not "1280 720" (that is multiplier 1280). - keep SIM + single ground-probe diagnostics (TEMP QA). --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Content/Hollywood/Site/M_Ground.uasset | 2 +- .../Hollywood/Site/M_KerbConcrete.uasset | 2 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 2 +- .../Content/Hollywood/Site/M_Stucco.uasset | 2 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 4 +- .../Source/GigLine/GigLineAssetTools.cpp | 65 +++++++++++++++++++ .../Source/GigLine/GigLineAssetTools.h | 34 ++++++++++ .../Source/GigLine/GigLineCharacter.cpp | 17 ++++- MEMORY.md | 10 +++ Tools/ue_build_demo_map.py | 30 +++++++-- 14 files changed, 157 insertions(+), 21 deletions(-) create mode 100644 Game/GigLine/Source/GigLine/GigLineAssetTools.cpp create mode 100644 Game/GigLine/Source/GigLine/GigLineAssetTools.h diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 125570e..9565dee 100644 --- a/Game/GigLine/Content/Hollywood/DemoMap.umap +++ b/Game/GigLine/Content/Hollywood/DemoMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c4090fdb335a9a2b3fe7a893e47fee03d76d166b0575fdf3f37221704f70ba9 -size 55060 +oid sha256:4182a28144b21d629e09e38ad2300e4537a9c5fa3ad5ed4ba2f91457f9551a99 +size 53223 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 1cd0afe..786ce6a 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a80b14b851004748983d1409c12d2f505600078134113cab2f53f48f5b48339 +oid sha256:3984f701d136b0931c56a87109f862ed16132ff210cc348cdbbc122f35d98478 size 7876 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 955f0ab..f526b3a 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f62893f52fdde9f16c7716d3eedfce3e90dad531c0679a014ce3f99895d3f9d5 +oid sha256:3a9537f05120a0cb5fc0bacac1fc44847e983ba757866316cb5e9a10fd142e53 size 7881 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 9a12b00..5382da3 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb8aabbf86e9649f2d57bd8687d49f7c871e2853037eae325bdcb9c52512abd6 +oid sha256:89fc0b99258c68c458dfc13e32d2ac8dd1e0977891d5992c38d123657f3a78d0 size 7896 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index d68fc4d..b5edd8a 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:adea68105cb7a3987e64a342d5a0aefe7dcd025c5aded71cdad6e535ad919f2d +oid sha256:a143de1f26d5fe2c053571b25e5ead359cfba9ed12b09b9e703fe269fda09161 size 7871 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 1a1eff9..daaddd6 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bf845145364d552eac6aee056cdb3c5c522661c662c5bc27a9d46982cba7fc8 +oid sha256:7d61219af92c368d63d810a880cd479568edd55ec1e13d3a0921a15b5f0bd029 size 7901 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 703aa07..2e27ef9 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08984db88f47a5760d87fc46e60581ec5917a994443f7e9be3dedd97aa1c3c58 +oid sha256:f09f4c1cb9c3d6dc98223d3046cab45453693794ea7089203f442ac4162c9fc6 size 7886 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index cb5efd9..5f5c400 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec26dc022aa25a6c39a904f16a56de9c54b9b13b81f50489db23353f7725986a +oid sha256:6ad2b4e24f36480b4089a509186d2cbbf8f03e50d9a6f50342420285acbed791 size 7871 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 4f38add..3d200fd 100644 --- a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset +++ b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c01d12a342abd31999ada2f3cc98defdff53bb831ed8f39dd6523654b2bc8d4 -size 335530 +oid sha256:2a1e69c853b4d19fd9e445b29d89dd6feb69057f17de54edfdec3d784b3ba779 +size 335528 diff --git a/Game/GigLine/Source/GigLine/GigLineAssetTools.cpp b/Game/GigLine/Source/GigLine/GigLineAssetTools.cpp new file mode 100644 index 0000000..481a8dc --- /dev/null +++ b/Game/GigLine/Source/GigLine/GigLineAssetTools.cpp @@ -0,0 +1,65 @@ +#include "GigLineAssetTools.h" + +#include "Engine/StaticMesh.h" +#include "PhysicsEngine/BodySetup.h" +#include "StaticMeshResources.h" + +void UGigLineAssetTools::SetComplexCollisionAsSimple(UStaticMesh* Mesh, bool bEnable) +{ + if (!Mesh) + { + return; + } + + UBodySetup* BodySetup = Mesh->GetBodySetup(); + if (!BodySetup) + { + return; + } + + BodySetup->CollisionTraceFlag = bEnable ? CTF_UseComplexAsSimple : CTF_UseSimpleAndComplex; + BodySetup->InvalidatePhysicsData(); + BodySetup->CreatePhysicsMeshes(); + Mesh->MarkPackageDirty(); + Mesh->PostEditChange(); +} + +void UGigLineAssetTools::ClearSimpleCollision(UStaticMesh* Mesh) +{ + if (!Mesh) + { + return; + } + + UBodySetup* BodySetup = Mesh->GetBodySetup(); + if (!BodySetup) + { + return; + } + + BodySetup->AggGeom.EmptyElements(); + BodySetup->InvalidatePhysicsData(); + BodySetup->CreatePhysicsMeshes(); + Mesh->MarkPackageDirty(); + Mesh->PostEditChange(); +} + +FString UGigLineAssetTools::GetCollisionComplexity(UStaticMesh* Mesh) +{ + if (!Mesh) + { + return TEXT("no mesh"); + } + + const UBodySetup* BodySetup = Mesh->GetBodySetup(); + if (!BodySetup) + { + return TEXT("no body setup"); + } + + const int32 Flag = static_cast(BodySetup->CollisionTraceFlag.GetValue()); + if (Flag == CTF_UseComplexAsSimple) { return TEXT("UseComplexAsSimple"); } + if (Flag == CTF_UseSimpleAsComplex) { return TEXT("UseSimpleAsComplex"); } + if (Flag == CTF_UseSimpleAndComplex) { return TEXT("UseSimpleAndComplex"); } + return TEXT("ProjectDefault"); +} diff --git a/Game/GigLine/Source/GigLine/GigLineAssetTools.h b/Game/GigLine/Source/GigLine/GigLineAssetTools.h new file mode 100644 index 0000000..cf48766 --- /dev/null +++ b/Game/GigLine/Source/GigLine/GigLineAssetTools.h @@ -0,0 +1,34 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "GigLineAssetTools.generated.h" + +class UStaticMesh; + +/** + * Small editor-side helpers for the headless asset pipeline scripts. + * + * Exposed to Python so tools can fix up imported meshes that the Interchange + * importer gives unusable default collision. The terrain is the motivating + * case: without complex-as-simple it gets a bounding-box proxy, so the whole + * street sits under an invisible 8 m box and the player walks on the box lid. + */ +UCLASS() +class GIGLINE_API UGigLineAssetTools : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** Make collision trace against the mesh triangles instead of a proxy hull. */ + UFUNCTION(BlueprintCallable, Category = "GigLine|Assets") + static void SetComplexCollisionAsSimple(UStaticMesh* Mesh, bool bEnable = true); + + /** Drop any generated simple collision (hulls/boxes) from the mesh. */ + UFUNCTION(BlueprintCallable, Category = "GigLine|Assets") + static void ClearSimpleCollision(UStaticMesh* Mesh); + + /** Report the current collision complexity: "SimpleAndComplex", "Simple", ... */ + UFUNCTION(BlueprintCallable, Category = "GigLine|Assets") + static FString GetCollisionComplexity(UStaticMesh* Mesh); +}; diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index a7fca8d..9a2482c 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -134,7 +134,7 @@ void AGigLineCharacter::BeginPlay() { if (APlayerController* PC = Cast(GetController())) { - PC->ConsoleCommand(TEXT("HighResShot 1280 720")); + PC->ConsoleCommand(TEXT("HighResShot 1280x720")); } }, 1.5f, false); @@ -146,15 +146,26 @@ void AGigLineCharacter::BeginPlay() { UCharacterMovementComponent* M = GetCharacterMovement(); UE_LOG(LogTemp, Log, - TEXT("GigLine SIM: t=%.2f Z=%.0f grav=%.1f fall=%d local=%d role=%d mode=%d ctrl=%s"), + TEXT("GigLine SIM: t=%.2f XYZ=(%.0f,%.0f,%.0f) grav=%.1f fall=%d local=%d role=%d mode=%d ctrl=%s"), GetWorld() ? GetWorld()->GetTimeSeconds() : -1.f, - GetActorLocation().Z, + GetActorLocation().X, GetActorLocation().Y, GetActorLocation().Z, M ? M->GetGravityZ() : 0.f, (M && M->IsFalling()) ? 1 : 0, IsLocallyControlled() ? 1 : 0, (int)GetLocalRole(), M ? (int)M->MovementMode : -1, GetController() ? *GetController()->GetName() : TEXT("NONE")); + if (UWorld* W = GetWorld()) + { + const FVector L = GetActorLocation(); + FHitResult Hit; + FCollisionQueryParams Q(TEXT("GigLineGroundProbe"), false, this); + const bool bHit = W->LineTraceSingleByChannel(Hit, + FVector(L.X, L.Y, 3000.f), FVector(L.X, L.Y, -3000.f), ECC_Visibility, Q); + UE_LOG(LogTemp, Log, TEXT("GigLine GROUND: top=%.0f actor=%s"), + bHit ? Hit.ImpactPoint.Z : -9999.f, + bHit && Hit.GetActor() ? *Hit.GetActor()->GetActorLabel() : TEXT("none")); + } }, 1.0f, true); } diff --git a/MEMORY.md b/MEMORY.md index 23c2bb5..5ebe9ac 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -6,6 +6,16 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. --- +## 2026-11-08 — The fall-through was an 8 m invisible box (QA branch) + +- **Symptom:** player "spawned above ground but never fell", sat at Z 909, `fall=0`, `mode=1` (Walking), no fall-through, view looked plausible so it was mistaken for working. +- **Cause:** `Terrain_DEM` had only a **bounding-box simple collision proxy** (Interchange default). A line trace down at *every* Y through the block hit `Demo_Terrain_DEM` at exactly **819** — the mesh bbox max Z. The whole street was under an invisible lid; the pawn stood on the box, 8 m above the road. +- **Proof:** a debug `LineTraceSingleByChannel` profile at Y −1912…+1447 reported `top=819 actor=Demo_Terrain_DEM` for the entire street. After the fix it reports `Sidewalk_North 395`, `Road_Hollywood 366`, `Sidewalk_South 317`. +- **Fix:** force complex-as-simple collision on the terrain. Python **cannot** set it (`StaticMesh`/`BodySetup` expose no such property; `dir()` shows only `lod_for_collision`). Added `UGigLineAssetTools::SetComplexCollisionAsSimple` in the C++ module (`BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple`, UE 5.8 no longer has `bUseComplexAsSimpleCollision`). +- **Second gotcha:** the flag lives on the **asset**, not the map. Setting it in a builder session and saving only the `.umap` loses it — `EditorAssetLibrary.save_loaded_asset()` is required, otherwise the next run reloads the bbox proxy. +- **Third gotcha:** `HighResShot 1280 720` means **multiplier 1280** (UE warns "the high resolution screenshot multiplier is too large for your system"). The correct form is `HighResShot 1280x720`. +- Resulting spawn: `(-13336, -1150, 600)` — inside the carved corridor, lands on the road at Z 456 (feet 368 on road top 366). + ## 2026-11-07 — Publication audit - MPFB 2.0.18's installed `blender_manifest.toml` identifies its code as GPL-3.0-or-later; its versioned `LICENSE.md` explicitly puts bundled mesh, texture, and rig assets under CC0 1.0. The earlier CC0 claim for the add-on code was wrong. diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 2c897b8..c7d907a 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -301,6 +301,19 @@ def build(): mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}") if mesh is None: continue + # The imported terrain carries a bounding-box simple collision proxy, + # which puts an invisible lid ~8 m over the whole block: the player + # walks on that box instead of the street. Force trace against the real + # triangles so the carved street corridor is actually open. + if "Terrain" in name: + asset = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}") + if asset: + unreal.GigLineAssetTools.set_complex_collision_as_simple(asset, True) + # The flag lives on the asset, not the map: persist it or the + # next session reloads the bbox proxy and the street is buried. + saved_asset = unreal.EditorAssetLibrary.save_loaded_asset(asset) + log(f"terrain collision: {unreal.GigLineAssetTools.get_collision_complexity(asset)} " + f"(saved asset: {saved_asset})") actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"Demo_{name}") if actor: comp = actor.static_mesh_component @@ -372,14 +385,17 @@ def build(): # sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. # The hero facade is on the SOUTH side at Y +1307. Stand on the north # sidewalk and face south (+Y, yaw 90) across the road at it. - # Z: the terrain here is ~Z 99-138. The capsule half-height is 87.5 cm, so - # feet must start ABOVE the surface or the point is embedded and UE rejects - # the PlayerStart ("NO PLAYERSTART with positive rating"), dropping the pawn - # at the world origin to fall through. Cap feet ~80 cm above grade: centre - # ~138+80+87 = 305, so Z 300 lets it drop cleanly and gravity settles it. - spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 300.0), "Demo_PlayerStart", + # CRITICAL: the terrain is only carved inside the street corridor, and + # terrain_from_dem.py --corridor 10.7 is a HALF-width around the road + # centreline (Y ~= -167), so the cut band is Y -1237..+902. A PlayerStart at + # Y -1700 sits OUTSIDE it, under ~8 m of uncut terrain: the capsule spawns + # embedded, depenetration pops it to Z ~909 (terrain top 819 + half-height + # 87.5) and it never touches the sidewalk. Y -1150 is on the north sidewalk + # (Y -1912..-1053) AND inside the cut, so the pavement is the ground. + # Z 600 is clear air; the capsule drops onto the pavement and settles. + spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1150.0, 600.0), "Demo_PlayerStart", unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0)) - log("staged: Demo_PlayerStart on the north sidewalk (cap above grade), facing south (+Y)") + log("staged: Demo_PlayerStart on the cut north sidewalk, facing south (+Y)") # 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. -- 2.47.3 From 055944fd33bebc2379d1e21cdce6bbe7cad7796b Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 11:30:47 +0100 Subject: [PATCH 10/13] docs: record terrain collision root cause; point Gameplay at the QA branch --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 612d625..350686e 100644 --- a/TODO.md +++ b/TODO.md @@ -89,10 +89,10 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep - [A] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; collision and details (crossing markings, kerb radii, aprons) are not built. Ground must follow the DEM: 3.94 m fall along the block, ~2 % crown between the gutters at ±10–11 m from the centreline, north sidewalk higher than south. Kerb height is an unresolved assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`). - [x] Materials and look development for all exported assets. **Done 2026-11-07, screenshots approved.** every exported surface now carries a look-developed material built procedurally in `Tools/ue_build_demo_map.py` (`ensure_material`): base colour with a world-space noise blend between two shades for large-scale variation, plus an explicit roughness and specular. Set is `M_Asphalt` 0.92, `M_Concrete` 0.88, `M_KerbConcrete` 0.85, `M_Ground` 0.98, `M_RoadPaint` 0.62, `M_FacadeStone` 0.72 (hero façade) and `M_Stucco` 0.86 (TCL, Tussaud's), assigned by mesh and by actor label. Materials are rebuilt on every run so a stale asset cannot mask a look change. The richer texture-based pass (baked/procedural brick, brick_facade and seamless_concrete generators) stays in the `0.1.4` façade patch. -### Gameplay - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `gameplay-attribution-0.1.0`) +### Gameplay - active - qa-input-collision-fix (branch `qa-input-collision-fix-0.1.0`) - [x] First-person movement, mouse look, and Enhanced Input bindings. Implemented 2026-11-07: `AGigLineCharacter` (C++, 1.75 m capsule, 1.60 m eye camera, 4 m/s walk, mouse look in deg/s, jump) as the default pawn, with five actions and an 11-entry context in `/Game/Input` built by `Tools/ue_create_input_assets.py`. **Not yet confirmed interactively** — launch PIE on `Test/ReviewMap` and walk. Screenshot review pending (`Screenshots/README.md`). -- [D] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**, a real game pause that releases the cursor) are implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. Collision now exists — terrain, road and sidewalks import as complex-as-simple, so a simple hull cannot ramp over the kerbs. **Not verified by hand**: walk, reset and pause need a human at the keyboard. No pause menu widget yet, only the pause itself. +- [-] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**) are implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. **Collision root cause found 2026-11-08:** the "fall-through" was never a fall — the imported `Terrain_DEM` had only a bounding-box simple collision proxy, so the player stood on an invisible lid 8 m above the street (every downward trace hit the terrain at Z 819). Fixed by forcing complex-as-simple collision on the terrain (`UGigLineAssetTools`, C++) and saving the asset; the demo now spawns at `(-13336, -1150, 600)`, lands on the road at Z 456 with the ground probe reporting `Road_Hollywood` at 366. Walking the block, reset and pause still need a human at the keyboard. - [x] Build the walkable demo level: terrain, street, and hero facade assembled into one map with a start point on the north sidewalk, so the demo is something a player can walk rather than a review staging. **Built 2026-11-07** by `Tools/ue_build_demo_map.py` → `/Game/Hollywood/DemoMap`: sun/sky, `Hero_Facade`, six site meshes imported with collision (`Terrain_DEM`, `Road_Hollywood`, both sidewalks and both kerbs), and a `Demo_PlayerStart` on the north sidewalk 20 m back from the frontage. Demo views in `Screenshots/demo/` re-rendered and ☑ approved 2026-11-07. - [x] Walkable sidewalks and a designated street crossing. Crossing chosen: mid-block signalised crossing, OSM way 385648319, at 130 m along the block (`Geo/verification/2026-11-06-street-profile.md`). Done 2026-11-07: continental zebra markings (0.5 m bars / 0.5 m gaps, 3 m band) at all three signalised crossings, no collision on the paint; kerbs were already flushed at the crossings. Enablers found on the way: the street surfaces were 100 % down-facing (invisible in engine) — winding flipped; the terrain buried the street — 10.7 m corridor cut around the centreline (sidewalks cover the transition, walk-check 0 holes); LFS-lockable `.blend`/`.umap` check out read-only — builders now chmod before saving and fail loudly. Screenshot ☑ approved 2026-11-07 (`Screenshots/demo/review-demo-crossing`); the corridor renders near-black in shadow — that was the **Fixed daytime lighting** item, now also done. - [x] Fixed daytime lighting and basic ambience. **Done 2026-11-07, screenshots approved.** Demo map now has a fixed daytime rig in `Tools/ue_build_demo_map.py`: sun pitch −50 / yaw 120 (intensity 4, warm 255/245/230, atmosphere sun light), sky light 2.6 with real-time capture, sky atmosphere, height fog (density 0.008), and a manual-exposure post-process volume (min = max brightness 1.0) so a shot cannot wash out or crush to black. Two root causes found while doing it: (a) **Blender flat diffuse colours do not survive the FBX→UE import** — every site mesh arrived white and the terrain arrived with `WorldGridMaterial`, which is what actually made the scene look blown out; explicit `M_Asphalt` / `M_Concrete` / `M_Ground` / `M_RoadPaint` materials are now created and assigned in the demo builder. (b) only **one of the three crossings** was reaching the game: each band was its own object and Blender renamed the duplicates `.001`/`.002`, which site assembly skips; all bands are now one `Crossing_Markings` object. Demo cameras also fixed: street-level sat inside the façade body, and the crossing camera aimed at the wrong crossing (s=36.6 instead of the designated s=130.1 at UE X −14956). Lighting is dynamic-only (`r.AllowStaticLighting=False`, all lights Movable) so the editor's "LIGHTING NEEDS TO BE REBUILT" warning no longer fires. All four demo views approved by the owner. -- 2.47.3 From 7930be38bb8f649daf80e2c388e9ba3b46694863 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 11:53:02 +0100 Subject: [PATCH 11/13] qa: scripted walk/reset/pause self-test, plus the reset bug it found The three behaviours listed as "needs a human at the keyboard" are now verifiable headlessly with -qaselftest (core ticker, so pausing the game does not freeze the test that proves pause works): walk 761 cm in 2 s, Z steady 456 PASS reset 0 cm from spawn PASS pause IsGamePaused() = 1 PASS unpause IsGamePaused() = 0 PASS Writing the test found a real bug: ResetToStart() teleported to a StartLocation captured in BeginPlay, which is the mid-air PlayerStart (Z 600) rather than the ground (Z 456), so every reset dropped the player 1.4 m back into the air. StartLocation is now captured on the first grounded frame. --- .../Source/GigLine/GigLineCharacter.cpp | 143 ++++++++++++++++++ .../GigLine/Source/GigLine/GigLineCharacter.h | 16 ++ 2 files changed, 159 insertions(+) diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index 9a2482c..9809d00 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -10,6 +10,10 @@ #include "InputMappingContext.h" #include "Engine/World.h" #include "TimerManager.h" +#include "Containers/Ticker.h" +#include "Kismet/GameplayStatics.h" +#include "Misc/CommandLine.h" +#include "Misc/Parse.h" AGigLineCharacter::AGigLineCharacter() { @@ -120,6 +124,31 @@ void AGigLineCharacter::BeginPlay() StartLocation = GetActorLocation(); StartRotation = GetActorRotation(); + // The PlayerStart sits above grade so the capsule cannot spawn embedded, so + // the BeginPlay position is mid-air. Re-capture once the pawn has landed, + // otherwise Reset drops the player 1.4 m back into the air every press. + // Repeat until the first grounded frame. A single late timer is wrong: it can + // fire after the player (or the QA self-test) has already started walking, + // which recorded a mid-stride position as "start". + GetWorldTimerManager().SetTimer(SettleHandle, [this]() + { + if (bQATestWalking) + { + return; // keep waiting; the test walks almost immediately + } + if (const UCharacterMovementComponent* Mov = GetCharacterMovement()) + { + if (Mov->IsMovingOnGround()) + { + StartLocation = GetActorLocation(); + StartRotation = GetActorRotation(); + GetWorldTimerManager().ClearTimer(this->SettleHandle); + UE_LOG(LogTemp, Log, TEXT("GigLine: start captured on ground at Z %.0f"), + StartLocation.Z); + } + } + }, 0.25f, true); + // Force game input mode + focus + mouse capture so keyboard/mouse reach the // pawn regardless of window focus quirks. if (APlayerController* PC = Cast(GetController())) @@ -167,6 +196,120 @@ void AGigLineCharacter::BeginPlay() bHit && Hit.GetActor() ? *Hit.GetActor()->GetActorLabel() : TEXT("none")); } }, 1.0f, true); + + if (FParse::Param(FCommandLine::Get(), TEXT("qaselftest"))) + { + StartQASelfTest(); + } +} + +void AGigLineCharacter::StartQASelfTest() +{ + // Driven by a core ticker rather than a world timer on purpose: pausing the + // game stops world timers, so a timer-based test would freeze the moment it + // proved pause works. + struct FQAState + { + float Elapsed = 0.0f; + FVector Start = FVector::ZeroVector; + FVector WalkEnd = FVector::ZeroVector; + float Step = 0.0f; + bool bWalkMoved = false; + bool bResetReturned = false; + bool bPausedOn = false; + bool bUnpausedBack = false; + }; + + TSharedPtr State = MakeShared(); + State->Start = GetActorLocation(); + UE_LOG(LogTemp, Log, TEXT("GigLine QA: start XYZ=(%.0f,%.0f,%.0f)"), + State->Start.X, State->Start.Y, State->Start.Z); + + FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this, + [this, State](float DeltaTime) + { + State->Elapsed += DeltaTime; + const float E = State->Elapsed; + + // 1.0 - 3.0 s: hold forward. Reset sets the Step marker so each phase + // runs exactly once without an ever-growing if-chain. + if (E >= 1.0f && State->Step < 1.0f) + { + State->Step = 1.0f; + // Measure from the settled position, not the mid-air spawn point. + State->Start = GetActorLocation(); + UE_LOG(LogTemp, Log, TEXT("GigLine QA: settled XYZ=(%.0f,%.0f,%.0f)"), + State->Start.X, State->Start.Y, State->Start.Z); + bQATestWalking = true; + } + if (bQATestWalking) + { + AddMovementInput(GetActorForwardVector(), 1.0f); + } + + // 3.0: stop and measure. + if (E >= 3.0f && State->Step < 2.0f) + { + State->Step = 2.0f; + bQATestWalking = false; + State->WalkEnd = GetActorLocation(); + const float Walked = FVector::Dist2D(State->Start, State->WalkEnd); + State->bWalkMoved = Walked > 200.0f; // >2 m in 2 s at 4 m/s + UE_LOG(LogTemp, Log, + TEXT("GigLine QA: walk moved %.0f cm in 2 s -> %s (Z %.0f -> %.0f)"), + Walked, State->bWalkMoved ? TEXT("PASS") : TEXT("FAIL"), + State->Start.Z, State->WalkEnd.Z); + } + + // 3.5: reset should return to the spawn point. + if (E >= 3.5f && State->Step < 3.0f) + { + State->Step = 3.0f; + ResetToStart(); + } + if (E >= 4.0f && State->Step < 4.0f) + { + State->Step = 4.0f; + const float Back = FVector::Dist(State->Start, GetActorLocation()); + State->bResetReturned = Back < 100.0f; + UE_LOG(LogTemp, Log, TEXT("GigLine QA: reset returned %.0f cm from spawn -> %s"), + Back, State->bResetReturned ? TEXT("PASS") : TEXT("FAIL")); + } + + // 4.5: pause, then confirm the world actually stopped. + if (E >= 4.5f && State->Step < 5.0f) + { + State->Step = 5.0f; + TogglePause(); + State->bPausedOn = UGameplayStatics::IsGamePaused(this); + UE_LOG(LogTemp, Log, TEXT("GigLine QA: pause -> paused=%d %s"), + State->bPausedOn ? 1 : 0, State->bPausedOn ? TEXT("PASS") : TEXT("FAIL")); + } + + // 5.5: unpause and confirm it comes back. + if (E >= 5.5f && State->Step < 6.0f) + { + State->Step = 6.0f; + TogglePause(); + State->bUnpausedBack = !UGameplayStatics::IsGamePaused(this); + UE_LOG(LogTemp, Log, TEXT("GigLine QA: unpause -> paused=%d %s"), + UGameplayStatics::IsGamePaused(this) ? 1 : 0, + State->bUnpausedBack ? TEXT("PASS") : TEXT("FAIL")); + } + + // 6.0: verdict, then stop ticking. + if (E >= 6.0f) + { + const bool bAllPass = State->bWalkMoved && State->bResetReturned + && State->bPausedOn && State->bUnpausedBack; + UE_LOG(LogTemp, Log, TEXT("GigLine QA: RESULT walk=%d reset=%d pause=%d unpause=%d -> %s"), + State->bWalkMoved ? 1 : 0, State->bResetReturned ? 1 : 0, + State->bPausedOn ? 1 : 0, State->bUnpausedBack ? 1 : 0, + bAllPass ? TEXT("PASS") : TEXT("FAIL")); + return false; + } + return true; + })); } void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.h b/Game/GigLine/Source/GigLine/GigLineCharacter.h index a0313a6..08c5e66 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.h +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.h @@ -38,6 +38,22 @@ protected: void ResetToStart(); void TogglePause(); + /** + * TEMP QA: scripted walk / reset / pause verification. + * + * Enabled only by the `-qaselftest` command-line switch, so it never runs in + * normal play. It exists because those three behaviours were listed as + * "needs a human at the keyboard" and a repeatable headless check beats + * waiting for one. + */ + void StartQASelfTest(); + + /** Set while the self-test is driving forward input. */ + bool bQATestWalking = false; + + /** Repeating timer that captures StartLocation on first ground contact. */ + FTimerHandle SettleHandle; + /** Where the player started, so reset returns there rather than the origin. */ UPROPERTY() FVector StartLocation = FVector::ZeroVector; -- 2.47.3 From cdfb24a2b1811f2dd606d55c80958d68c40c386f Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 11:54:25 +0100 Subject: [PATCH 12/13] qa: swept ground transect closes the traversal release check 84 samples across the north sidewalk, carriageway and south sidewalk (X -14900..-9420) come back with 0 holes and ground Z 317..395, so the block is traversable rather than merely spawnable. TODO: sidewalks/collision item now records the collision state and what is genuinely left (kerb radii, aprons, kerb-height assumption); the traversal release check is ticked with the measurement. --- .../Source/GigLine/GigLineCharacter.cpp | 38 ++++++++++++++++++- TODO.md | 6 +-- Tools/README.md | 7 ++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index 9809d00..45736cd 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -297,8 +297,42 @@ void AGigLineCharacter::StartQASelfTest() State->bUnpausedBack ? TEXT("PASS") : TEXT("FAIL")); } - // 6.0: verdict, then stop ticking. - if (E >= 6.0f) + // 6.0: ground transect - sweep both sidewalks and the carriageway and + // count holes, so "can traverse the block without falling through" + // is measured rather than assumed. + if (E >= 6.0f && State->Step < 7.0f) + { + State->Step = 7.0f; + UWorld* W = GetWorld(); + int32 Holes = 0, Samples = 0; + float Lowest = 1.0e9f, Highest = -1.0e9f; + static const float Lanes[] = {-1400.0f, -167.0f, 1000.0f}; // N walk, road, S walk + for (float LaneY : Lanes) + { + for (float X = -14900.0f; X <= -9420.0f; X += 200.0f) + { + FHitResult Hit; + FCollisionQueryParams Q(TEXT("GigLineTransect"), false, this); + const bool bHit = W->LineTraceSingleByChannel(Hit, + FVector(X, LaneY, 3000.f), FVector(X, LaneY, -3000.f), + ECC_Visibility, Q); + ++Samples; + if (!bHit) + { + ++Holes; + continue; + } + Lowest = FMath::Min(Lowest, Hit.ImpactPoint.Z); + Highest = FMath::Max(Highest, Hit.ImpactPoint.Z); + } + } + UE_LOG(LogTemp, Log, + TEXT("GigLine QA: transect %d samples, %d holes, ground Z %.0f..%.0f -> %s"), + Samples, Holes, Lowest, Highest, Holes == 0 ? TEXT("PASS") : TEXT("FAIL")); + } + + // 7.0: verdict, then stop ticking. + if (E >= 7.0f) { const bool bAllPass = State->bWalkMoved && State->bResetReturned && State->bPausedOn && State->bUnpausedBack; diff --git a/TODO.md b/TODO.md index 350686e..d38f9e3 100644 --- a/TODO.md +++ b/TODO.md @@ -86,13 +86,13 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep - [x] Model hero façade for the El Capitan Entertainment Centre site. **Second pass built 2026-11-07** (proportions recalibrated from the 2009 photograph: entablature 3.4 m, parapet 2.2 m, architrave and cornice bands, square pedestals — the first pass had 5.7 m of blank wall above the capitals and read as a warehouse). **First pass built 2026-11-07** (`Tools/hero_facade_build.py` → `Art/blender/hero/hero-facade-2026-11-07.blend` + JSON part report with evidence per part → `Tools/ue_hero_facade_import.py` → `/Game/Hollywood/ElCapitan/SM_HeroFacade`, 1892 tris, 4 materials, placed on the measured building line). Six columns at 4.96 m over the 25.75 m frontage, plinth, three portal flights (5 risers, not the old 8), entablature 2.2 m, parapet 1.6 m with motto band and medallions. **Screenshot approved** (`Screenshots/review-hero-portals`, `review-hero-facade-front/oblique`). Next pass: banner and window placement, then materials. - [x] Model surrounding block frontage. **Done 2026-11-07, screenshots approved.** The five remaining south-side buildings verified for the block (`Geo/verification/2026-11-06-el-capitan-block.md`) are exported in place from the OSM blockout by `Tools/export_block_frontage.py` -> `Tools/ue_import_block_frontage.py` -> `/Game/Hollywood/Frontage/`, and staged by the demo builder: 6922, 6902, 6838 El Capitan Theatre, 6822 Budget, 6806-6820, 6800 Lee Drug. **Adding them exposed a pre-existing placement bug**: UE south is +Y (the south sidewalk is Y +718..+1447) but the hero facade sat at Y -1450 (north side) and X -12024..-9420, i.e. 26 m east of its own footprint and on the wrong side of the street; the two north-side buildings were likewise on the south side. The hero is now at X -13336 / Y +1307 (inside the frontage gap between 6902 and 6838) and the north buildings are back on the north side (Y -1802..-9431). The sun was raised to pitch -62 so both frontages read instead of one being silhouetted. - [x] Model simplified background buildings directly across Hollywood Boulevard. Done 2026-11-07, the two in-scope north-side buildings — TCL Chinese Theatre (SM_TCL_Chinese_Theatre) and Madame Tussaud's (SM_Madame_Tussauds) — taken from the verified OSM blockout volumes and exported with `ue_metric_v1`, with a Y-mirror so they land north of Hollywood Blvd across the street from the hero (TCL spans Unreal Y 1802..9431, Tussaud's 1888..7586; road centreline ≈ 0, hero façade at Y −1450). Staged in the DemoMap as `North_TCL_Chinese` / `North_Madame_Tussauds`; demo shot `review-03-demo-north-buildings` captured. Simple flat facade materials (surfaces are the `0.1.4` patch). -- [A] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; collision and details (crossing markings, kerb radii, aprons) are not built. Ground must follow the DEM: 3.94 m fall along the block, ~2 % crown between the gutters at ±10–11 m from the centreline, north sidewalk higher than south. Kerb height is an unresolved assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`). +- [A] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; crossing markings were built 2026-11-07. Ground follows the DEM: 3.94 m fall along the block, north sidewalk higher than south. **Collision done and verified 2026-11-08:** every site mesh is STATIC with `BlockAll`, the terrain is forced complex-as-simple (a bbox proxy had buried the street 8 m deep), and a swept transect of both sidewalks plus the carriageway reports 84 samples, **0 holes**, ground Z 317..395. Remaining: kerb radii at the corners and apron details, and the kerb height is still an assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`). - [x] Materials and look development for all exported assets. **Done 2026-11-07, screenshots approved.** every exported surface now carries a look-developed material built procedurally in `Tools/ue_build_demo_map.py` (`ensure_material`): base colour with a world-space noise blend between two shades for large-scale variation, plus an explicit roughness and specular. Set is `M_Asphalt` 0.92, `M_Concrete` 0.88, `M_KerbConcrete` 0.85, `M_Ground` 0.98, `M_RoadPaint` 0.62, `M_FacadeStone` 0.72 (hero façade) and `M_Stucco` 0.86 (TCL, Tussaud's), assigned by mesh and by actor label. Materials are rebuilt on every run so a stale asset cannot mask a look change. The richer texture-based pass (baked/procedural brick, brick_facade and seamless_concrete generators) stays in the `0.1.4` façade patch. ### Gameplay - active - qa-input-collision-fix (branch `qa-input-collision-fix-0.1.0`) - [x] First-person movement, mouse look, and Enhanced Input bindings. Implemented 2026-11-07: `AGigLineCharacter` (C++, 1.75 m capsule, 1.60 m eye camera, 4 m/s walk, mouse look in deg/s, jump) as the default pawn, with five actions and an 11-entry context in `/Game/Input` built by `Tools/ue_create_input_assets.py`. **Not yet confirmed interactively** — launch PIE on `Test/ReviewMap` and walk. Screenshot review pending (`Screenshots/README.md`). -- [-] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**) are implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. **Collision root cause found 2026-11-08:** the "fall-through" was never a fall — the imported `Terrain_DEM` had only a bounding-box simple collision proxy, so the player stood on an invisible lid 8 m above the street (every downward trace hit the terrain at Z 819). Fixed by forcing complex-as-simple collision on the terrain (`UGigLineAssetTools`, C++) and saving the asset; the demo now spawns at `(-13336, -1150, 600)`, lands on the road at Z 456 with the ground probe reporting `Road_Hollywood` at 366. Walking the block, reset and pause still need a human at the keyboard. +- [x] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**) implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. **Collision root cause found 2026-11-08:** the "fall-through" was never a fall — the imported `Terrain_DEM` had only a bounding-box simple collision proxy, so the player stood on an invisible lid 8 m above the street (every downward trace hit the terrain at Z 819). Fixed by forcing complex-as-simple collision on the terrain (`UGigLineAssetTools`, C++) and saving the asset; the demo spawns at `(-13336, -1150, 600)` and lands on the road at Z 456. **Verified 2026-11-08** by a scripted check (`-qaselftest`, core ticker so pause cannot freeze it): walk 761 cm in 2 s with Z steady, reset returns 0 cm from spawn, pause reports `IsGamePaused()` 1, unpause 0 — `RESULT ... PASS`. Writing it found a real bug: `ResetToStart()` teleported to the mid-air PlayerStart (Z 600), dropping the player 1.4 m on every reset; `StartLocation` is now captured on the first grounded frame. A manual smoke test is still worth doing before the release tag. - [x] Build the walkable demo level: terrain, street, and hero facade assembled into one map with a start point on the north sidewalk, so the demo is something a player can walk rather than a review staging. **Built 2026-11-07** by `Tools/ue_build_demo_map.py` → `/Game/Hollywood/DemoMap`: sun/sky, `Hero_Facade`, six site meshes imported with collision (`Terrain_DEM`, `Road_Hollywood`, both sidewalks and both kerbs), and a `Demo_PlayerStart` on the north sidewalk 20 m back from the frontage. Demo views in `Screenshots/demo/` re-rendered and ☑ approved 2026-11-07. - [x] Walkable sidewalks and a designated street crossing. Crossing chosen: mid-block signalised crossing, OSM way 385648319, at 130 m along the block (`Geo/verification/2026-11-06-street-profile.md`). Done 2026-11-07: continental zebra markings (0.5 m bars / 0.5 m gaps, 3 m band) at all three signalised crossings, no collision on the paint; kerbs were already flushed at the crossings. Enablers found on the way: the street surfaces were 100 % down-facing (invisible in engine) — winding flipped; the terrain buried the street — 10.7 m corridor cut around the centreline (sidewalks cover the transition, walk-check 0 holes); LFS-lockable `.blend`/`.umap` check out read-only — builders now chmod before saving and fail loudly. Screenshot ☑ approved 2026-11-07 (`Screenshots/demo/review-demo-crossing`); the corridor renders near-black in shadow — that was the **Fixed daytime lighting** item, now also done. - [x] Fixed daytime lighting and basic ambience. **Done 2026-11-07, screenshots approved.** Demo map now has a fixed daytime rig in `Tools/ue_build_demo_map.py`: sun pitch −50 / yaw 120 (intensity 4, warm 255/245/230, atmosphere sun light), sky light 2.6 with real-time capture, sky atmosphere, height fog (density 0.008), and a manual-exposure post-process volume (min = max brightness 1.0) so a shot cannot wash out or crush to black. Two root causes found while doing it: (a) **Blender flat diffuse colours do not survive the FBX→UE import** — every site mesh arrived white and the terrain arrived with `WorldGridMaterial`, which is what actually made the scene look blown out; explicit `M_Asphalt` / `M_Concrete` / `M_Ground` / `M_RoadPaint` materials are now created and assigned in the demo builder. (b) only **one of the three crossings** was reaching the game: each band was its own object and Blender renamed the duplicates `.001`/`.002`, which site assembly skips; all bands are now one `Crossing_Markings` object. Demo cameras also fixed: street-level sat inside the façade body, and the crossing camera aimed at the wrong crossing (s=36.6 instead of the designated s=130.1 at UE X −14956). Lighting is dynamic-only (`r.AllowStaticLighting=False`, all lights Movable) so the editor's "LIGHTING NEEDS TO BE REBUILT" warning no longer fires. All four demo views approved by the owner. @@ -110,7 +110,7 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep ### Release checks -- [ ] Player can traverse both sides of the demo without falling through geometry. Blender pre-check passes: 0 raycast holes along both sidewalks and the designated crossing; worst seam step 15.8 cm at the sidewalk's outer edge (`Art/blender/README.md`). Engine traversal still to be tested. +- [x] Player can traverse both sides of the demo without falling through geometry. **Verified 2026-11-08:** Blender pre-check plus an in-engine swept transect (`-qaselftest`) over the north sidewalk, carriageway and south sidewalk from X -14900 to -9420 — 84 samples, 0 holes, ground Z 317..395 (sidewalk 395, road 366, south walk 317). The player spawns at Z 456 and stays there for the whole run (`fall=0`, single Z). - [ ] Building placement, street scale, and site identity checked against references. - [ ] No blocking collision or packaging defects. - [ ] 60 FPS at 1440p on the dev workstation (Bazzite, Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB) using a documented quality preset; record upscaling state; report performance without frame generation. diff --git a/Tools/README.md b/Tools/README.md index 26c5939..3be42fa 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -51,3 +51,10 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | --- | --- | | `render_screenshots.py` / `render_screenshots.sh` | Rebuilds the review map, runs the game, captures the shot list into `Screenshots/` | | `judge_screenshot.py` | Vision-model pre-check: judges a screenshot against an expected-scene description (`--image` + `--expect`, or `--shots` for every shot in the README). Reads `OPENROUTER_API_KEY` from `.env` (ignored). Optional helper — a human must still approve. | + +## Gameplay QA + +| Command | Purpose | +| --- | --- | +| `UnrealEditor /Game/Hollywood/DemoMap -game -qaselftest` | Headless behaviour check: drives forward input for 2 s, then resets and pauses. Logs `GigLine QA: ... PASS/FAIL` lines and a final `RESULT`. Runs only with `-qaselftest`, so normal play is unaffected. | +| `GigLine SIM` / `GigLine GROUND` log lines | Emitted every second by `AGigLineCharacter` (TEMP QA): position, fall state, movement mode, and what the ground probe hits underneath. Remove before release. | -- 2.47.3 From dd391ed6ff143019e722dfef1efea61bc6000bbd Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 11:54:32 +0100 Subject: [PATCH 13/13] memory: reset-to-midair bug and the -qaselftest harness --- MEMORY.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MEMORY.md b/MEMORY.md index 5ebe9ac..2b6f8fc 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -6,6 +6,12 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. --- +## 2026-11-08 — Reset was returning to mid-air; QA self-test switch + +- **`ResetToStart()` restored a `StartLocation` captured in `BeginPlay`**, which is the PlayerStart position *above* grade (Z 600) so the capsule never spawns embedded. Every reset therefore dropped the player 1.4 m back into the air. Fix: capture `StartLocation` on the first grounded frame (repeating 0.25 s timer, cleared once captured) — not a one-shot timer, which can fire after the player has already started walking and record a mid-stride position. +- **`-qaselftest`** drives the checks headlessly and logs `GigLine QA: ... PASS/FAIL`. It runs on a **core ticker, not a world timer**: pausing the game stops world timers, so a timer-driven test freezes exactly when it proves pause works. +- Current results: walk 746 cm / 2 s with Z steady, reset 0 cm from spawn, pause `IsGamePaused()` 1, unpause 0, transect 84 samples / 0 holes (ground Z 317..395). + ## 2026-11-08 — The fall-through was an 8 m invisible box (QA branch) - **Symptom:** player "spawned above ground but never fell", sat at Z 909, `fall=0`, `mode=1` (Walking), no fall-through, view looked plausible so it was mistaken for working. -- 2.47.3