# Architecture Decisions ## ADR-1: Dynamic-only lighting for the demo - **Context**: The demo is a fixed daytime scene. Stationary lights demand a lighting build and produce "LIGHTING NEEDS TO BE REBUILT" warnings. - **Decision**: `r.AllowStaticLighting=False`, all lights `MOVABLE`. - **Consequences**: No bake step, no warning, simpler iteration. Slightly higher GPU cost per frame (no baked shadows) — acceptable for the demo scope. ## ADR-2: Materials authored in Unreal, not imported from Blender - **Context**: FBX→Unreal import maps all Blender diffuse colours to white. Flat colour materials do not survive the pipeline. - **Decision**: Build all materials (asphalt, concrete, stucco, façade stone, road paint) directly in Unreal with procedural nodes. - **Consequences**: Materials cannot be previewed in Blender look-dev. Must round-trip to Unreal for visual check. But: full PBR control, world-space noise, no import surprise. ## ADR-3: Complex-as-simple collision on terrain via C++ helper - **Context**: UE 5.8 removed `bUseComplexAsSimpleCollision`. Python cannot set `CollisionTraceFlag` on `StaticMesh`/`BodySetup` at all. The Interchange default is bbox-only simple collision, which placed an invisible lid 8 m above the street. - **Decision**: `UGigLineAssetTools::SetComplexCollisionAsSimple` (C++) forces `CTF_UseComplexAsSimple` and saves the asset. - **Consequences**: One C++ helper class. The flag must be saved on the asset (not just the map). No Python workaround exists. ## ADR-4: Reset captures StartLocation on first grounded frame - **Context**: `BeginPlay` captures `StartLocation` at the PlayerStart, which is above grade (Z 600) so the capsule does not spawn embedded. Every reset dropped the player 1.4 m back into the air. - **Decision**: Repeating 0.25 s timer captures `StartLocation` once the character reports `IsMovingOnGround()`, then clears itself. - **Consequences**: Reset is correct. A one-shot timer at 2 s is wrong (fires after walking begins, records mid-stride position). ## ADR-5: Core ticker for QA self-test, not world timer - **Context**: Pausing the game stops world timers. A timer-driven test freezes exactly when it proves pause works. - **Decision**: `FTSTicker::GetCoreTicker()` drives the `-qaselftest` harness. - **Consequences**: Test runs even while paused. Must be enabled explicitly (`-qaselftest`), never in normal play. ## ADR-6: South = +Y, north = −Y in Unreal - **Context**: The street mesh established this convention: `Sidewalk_North` has Y range −1912..−1053, `Sidewalk_South` has +718..+1447. - **Decision**: Adopt it project-wide. Never place a building by assumption; always verify against a street mesh or neighbour. - **Consequences**: Matches Blender’s north→+Y after the Y-flip in the `ue_metric_v1` export preset. ## ADR-7: Headless Blender via bpy scripts, not MCP - **Context**: Blender skill packs assume an MCP server; the project does not run one. - **Decision**: All Blender work driven by Python scripts in `Tools/`. Skill packs used as method guides (checklists, naming, proportions) only. - **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI.