- Converted 13 individual .adoc section files to .md via asciidoctor→docbook5→pandoc(gfm) + post-processing, placed alongside the .adoc sources in Docs/Architecture/ - Rebuilt architecture.md from the individual section .md files - Ran prettier --write on all .md in the repo (23 files changed): normalised table column alignment, trailing whitespace, line breaks - Added .agents/ to .prettierignore (vendored third-party content) - All .md now passes prettier --check
3.2 KiB
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 lightsMOVABLE. -
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 setCollisionTraceFlagonStaticMesh/BodySetupat all. The Interchange default is bbox-only simple collision, which placed an invisible lid 8 m above the street. -
Decision:
UGigLineAssetTools::SetComplexCollisionAsSimple(C++) forcesCTF_UseComplexAsSimpleand 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:
BeginPlaycapturesStartLocationat 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
StartLocationonce the character reportsIsMovingOnGround(), 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-qaselftestharness. -
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_Northhas Y range −1912..−1053,Sidewalk_Southhas +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_v1export 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.