From 9a3784683f7bdcf175c5fad1aecee422852c35e7 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:00:41 +0100 Subject: [PATCH 01/13] feat(materials): masonry coursing, glass/banner materials, per-slot assignment, telephoto elevations 0.1.4 materials pass plus the camera fix levelling exposed. Materials (Tools/ue_build_demo_map.py): - ensure_material() gains optional masonry coursing (world-space course height, joint band, joint tint) and roughness variation, so facades read as coursed stone/stucco instead of one flat specular plane. - WorldPosition is in centimetres: course heights in metres are scaled by 100 (the first attempt divided by 0.55 and the coursing was invisible). - connect_material_expressions returns False rather than raising, so every link now goes through a link() helper that logs failures; UE has no MaterialExpressionLess, so the joint mask is Frac -> Divide -> OneMinus -> Clamp. - New materials: M_FacadeStoneShadow, M_FacadeGlass (specular 0.62 ~ IOR 1.5), M_FacadeBanner. - Material assignment is now per SLOT keyed on the imported slot material name. It was slot-0 only, which on the hero is the banner, so stone, shadow stone and glazing still wore flat Blender materials. Hero now 4/4 slots. Cameras (GigLineShotActor + ue_build_review_map): - Optional per-shot ShotFieldOfView; the shot tuples take a 4th element. - Elevations use a 42-55 deg lens from across the street: with the cameras levelled (0.1.2 follow-up) the building was small in frame, and tilting to fill it is what breaks the verticals. hero-facade-front sky 39% -> 20%, demo-spawn-view 23% -> 16%. Screenshots re-rendered for the whole set. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 3 + .../Hollywood/Site/M_FacadeGlass.uasset | 3 + .../Hollywood/Site/M_FacadeStone.uasset | 4 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 3 + .../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 | 4 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- Game/GigLine/Content/Test/ReviewMap.umap | 4 +- .../Source/GigLine/GigLineShotActor.cpp | 10 ++ .../GigLine/Source/GigLine/GigLineShotActor.h | 10 ++ MEMORY.md | 25 +++ Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- Screenshots/review-character.png | 4 +- Screenshots/review-ground-contact.png | 4 +- Screenshots/review-hero-facade-front.png | 4 +- Screenshots/review-hero-facade-oblique.png | 4 +- Screenshots/review-hero-portals.png | 4 +- Screenshots/review-npc-prefab.png | 4 +- Screenshots/review-overview.png | 4 +- Screenshots/review-player-placement.png | 4 +- Screenshots/review-scale-cubes.png | 4 +- Screenshots/review-shadow-compare.png | 4 +- Tools/ue_build_demo_map.py | 147 ++++++++++++++++-- Tools/ue_build_review_map.py | 15 +- 32 files changed, 243 insertions(+), 57 deletions(-) create mode 100644 Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset create mode 100644 Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset create mode 100644 Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 86b0f4d..21ed7b4 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:3d9a16b41394efe0b040d919d1ffa8c9f06b5967cdb4a4306c3a04aa49fdba10 -size 54464 +oid sha256:5544cf164533ce61534d98075bbf41e6fcaa91f340492c82ce10154a75028013 +size 55058 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index ee81ce5..14f0f6c 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:8b3b5a17f3a916bb27858051e5ba88b4266a420898128e3fb26577b4ca92ff15 +oid sha256:8f56c5b314d6f7844f2833beefe709676368ccbd2a45ae30f48ec0ae83da4af2 size 7876 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 30f1d14..2cc2114 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:35ef03e01d6cc6aee00d7bdea4063213b1b3f4ec717838f6694dcdfe85e593a8 +oid sha256:2471e65a5e1809aa78abdb6e52de01e72a426ed9c825c2d90e7a0f8bf1dda400 size 7881 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset new file mode 100644 index 0000000..44e5528 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:832e10baec763e12727cd6be226ea1fb9ced63aadaf55f25cd5323a7a8fec8c4 +size 8667 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset new file mode 100644 index 0000000..dd46ad6 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be4d972ad3655d1c9ec383b69f66c7ead7c8279e9425dfc442dfd7c7393e78a8 +size 8662 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 99136e1..0e63bee 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:092d4108e04ea0a90902249a2ff29480a61ce0001963fdbb7fffa7788facee46 -size 7896 +oid sha256:bf20102e53609e517647200ad44528284dbc5bd26e00d1b41467e3db2ca3c6f1 +size 12131 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset new file mode 100644 index 0000000..f1d76f5 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8876871cf45828b2da703e3082bc3bb5a2fb63263958ca5314b4072fe7418ec1 +size 12161 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index fea4b51..3317c51 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:6bb3fc7e0ba1c40897838d8ab01b053f467da130e54c19a91e46b61a15b09e2b +oid sha256:adb033875c629572815f0d27b94a16bdc3b869344419ad7343b59c8ccdc1e850 size 7871 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index a3c6748..1becb71 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:ef17b5d0ed660a13a60b294a9d36ab99787336b2655e4f350fcc2780e5fc5d04 +oid sha256:715ec7945cf7eb69f07755252ccc4b39ea1cbfad1a55eddb99616517ef5def42 size 7901 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 7e38022..91ddd02 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:60d614ddb19422a8fcca5474f731ef5640f7d7d60c6ef59780914c15f8367388 +oid sha256:dcfda0b94f7141564391069629682cac71c9152100038a5e927a2355c89c869a size 7886 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index de5a3bb..9f20fe3 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:38bbe13637e5a27860b2b0cb2d75d9802446a8ee481825de46bd04bbf0b6f586 -size 7871 +oid sha256:940807fd433a5eab6726c830b4c54874a4acf3c352e415e6de71f0f6ed1b4ede +size 8637 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 263ef74..1dc6b23 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:22e56a42763de7a73b4d8974a3e8709d89ebdc666bde2ce5d21dea699cee3198 +oid sha256:a7b7c866693db2f80334baeb66af3ff2585368a503af806c3d78cae71f328b17 size 357901 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index a36c204..14c9200 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c34637f8b929cab7a68725f550f65ebdfac790cb5af5e2349ef661dccd8faa0 -size 28706 +oid sha256:6bef39374cc710661ddf2f15194d28b298bfa0d0adcf25668740712381c2ba29 +size 28811 diff --git a/Game/GigLine/Source/GigLine/GigLineShotActor.cpp b/Game/GigLine/Source/GigLine/GigLineShotActor.cpp index ba0cc15..878011c 100644 --- a/Game/GigLine/Source/GigLine/GigLineShotActor.cpp +++ b/Game/GigLine/Source/GigLine/GigLineShotActor.cpp @@ -1,6 +1,7 @@ #include "GigLineShotActor.h" #include "Camera/CameraActor.h" +#include "Camera/CameraComponent.h" #include "Engine/World.h" #include "GameFramework/PlayerController.h" #include "UnrealClient.h" @@ -83,6 +84,15 @@ void AGigLineShotActor::NextShot() Params.ObjectFlags |= RF_Transient; CurrentCamera = GetWorld()->SpawnActor( ACameraActor::StaticClass(), ShotLocations[CurrentShot], ShotRotations[CurrentShot], Params); + if (ShotFieldOfView.IsValidIndex(CurrentShot) && ShotFieldOfView[CurrentShot] > 0.0f) + { + if (UCameraComponent* Cam = CurrentCamera->GetCameraComponent()) + { + // Long lens for elevations: fills the frame from across the street + // without pitching the camera, so verticals stay parallel. + Cam->SetFieldOfView(ShotFieldOfView[CurrentShot]); + } + } PlayerController->SetViewTarget(CurrentCamera); SettleCountdown = SettleSeconds; diff --git a/Game/GigLine/Source/GigLine/GigLineShotActor.h b/Game/GigLine/Source/GigLine/GigLineShotActor.h index 9aa0777..4578fca 100644 --- a/Game/GigLine/Source/GigLine/GigLineShotActor.h +++ b/Game/GigLine/Source/GigLine/GigLineShotActor.h @@ -36,6 +36,16 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite) TArray ShotNames; + /** + * Optional per-shot field of view (degrees), same index as ShotLocations. + * Zero or missing entries keep the engine default (90 deg). Elevation shots + * taken from across the street need a long lens: a level camera 45 m out with + * the default FOV leaves the building small in frame, and tilting it up to fill + * the frame is what breaks the verticals. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray ShotFieldOfView; + /** Seconds to let the view target settle before the capture. Resolution comes from the runner's -ResX/-ResY. */ UPROPERTY(EditAnywhere, BlueprintReadWrite) float SettleSeconds = 0.35f; diff --git a/MEMORY.md b/MEMORY.md index a126f8c..1006f57 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -525,3 +525,28 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and documents the two optional API keys; architecture 07 + combined doc corrected the build-output path. + +## 2026-11-09 — 0.1.4 materials + architecture-camera pass + +- **Material graphs**: `ensure_material()` now supports masonry coursing + (`courses_m`) and roughness variation. **UE's `WorldPosition` is in + CENTIMETRES**, so a course height in metres must be ×100 — the first attempt + divided by 0.55 and produced coursing 100× too fine to see. +- **`connect_material_expressions` returns False instead of raising**, so an + unknown pin name leaves an input unconnected and the material silently compiles + with a default (a mask of 0 is invisible). All links now go through a `link()` + helper that logs failures. There is also **no `MaterialExpressionLess`**; use + `Frac → Divide → OneMinus → Clamp` for a soft threshold band, or + `MaterialExpressionStep`/`SmoothStep` if a hard one is wanted. +- **Material assignment was slot-0-only**, which on the hero is the *banner* — the + stone, shadowed stone and glazing kept their flat Blender materials. Assignment + is now per slot, keyed on the imported slot material's name + (`glass`/`banner`/`stoneshadow`/`stone`/`roof`/`wall`). Hero: 4/4 slots. +- New materials: `M_FacadeStoneShadow` (same coursing, a stop darker), + `M_FacadeGlass` (roughness 0.14, specular 0.62 ≈ IOR 1.5 expressed without a + refraction pass), `M_FacadeBanner` (the one saturated surface). +- **Camera FOV**: levelling the elevation cameras left the building small in frame + (39 % sky). Added optional per-shot `ShotFieldOfView` to `AGigLineShotActor` and + a 4th element on the shot tuples; a long lens (42–55°) fills the frame from + across the street while the camera stays level, so verticals stay parallel. + `review-hero-facade-front` sky 39 → 20 %, `demo-spawn-view` 23 → 16 %. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 72ccf7a..4476642 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:380647ec83dcdc1d1796a3f6a033dfcb86d634c8312faf0c4332c795a12dcc81 -size 2592475 +oid sha256:a21bd2e7d32099ee8490de63551e817e144ce74cade34dbf7854bfc125a899d0 +size 2756050 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 05d5445..83da020 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f01bd1af993306a5de88397e6f0bcc79f64f0167b06353e54b8cc0719ff4881a -size 2261865 +oid sha256:f93538fc3aa3d3b0a83c5b0ba9637445ef66a2fba845e0dd3f39fc04ddac06ac +size 2265651 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index ad2ad70..4a928e0 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63d49011bcf0ab52b96c4ec7b27eeba3d27de3bbbf951a8f8e0ac09117995f3e -size 2628687 +oid sha256:d4a781b779e59facdcd0275cc147ea4f9a5cf9f36c9061c400eca79f69aceb2d +size 2843274 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index f07d483..2da3f96 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d87896644e71e7a66b18fb9997d76d51a05cce5fa6fc97e3f698a45258d0b306 -size 2468639 +oid sha256:744bb6b3ddcf3130e11d93c1ba6eb0b71e5968629d2517401cd36ecc74e020c8 +size 2704752 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index 82f9181..d1c2dc1 100644 --- a/Screenshots/review-character.png +++ b/Screenshots/review-character.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44df56de845bde433eb1d6de2294462c9933d8f6a800a4f3cdc01793c5b1497b -size 2528497 +oid sha256:e794bb187f5f1cd27cfcdf89a504ce22a33a529cc3244ba5381af930c33e778d +size 2539235 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index 4dbada6..7c5785b 100644 --- a/Screenshots/review-ground-contact.png +++ b/Screenshots/review-ground-contact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9043e2e90ffead898262bae5c6f8ad0bbc700c5f52aa398ae6f486a858ac4af0 -size 1749002 +oid sha256:586c717e6066aed87c6f7caf1f566789c5ecf17cecf786cd7894a8e4d75afeb8 +size 1756397 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index d523315..ccb004b 100644 --- a/Screenshots/review-hero-facade-front.png +++ b/Screenshots/review-hero-facade-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8c70fa7806c12a0cdbf2196b5f52f82f402e1b806f067e48a98970a94393e41 -size 1436443 +oid sha256:e6ad9425e1cda90135358752c655a70a08e0d449629d1f5075b31fcc272eb601 +size 2297053 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index 6428303..410d1ff 100644 --- a/Screenshots/review-hero-facade-oblique.png +++ b/Screenshots/review-hero-facade-oblique.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c53e905de74ffb3783f2895ed667e352179b7bc411246b3b5bcdae88a5b299f9 -size 1306447 +oid sha256:9731498fc8054b94de923cb311f38c31be95af642786bf64f6015db357823559 +size 1516067 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index c54cf5b..4f8adb4 100644 --- a/Screenshots/review-hero-portals.png +++ b/Screenshots/review-hero-portals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7455b6cc2a5a81ed1848956c41555a7775b705c24a34a0cbd8e0d0717e8d499 -size 1321848 +oid sha256:8d02821b21a4b61fed206bb5a8f38ff3b1ab27a0cc16e26cfd0639eb1f5a32ab +size 1327638 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index 74c6464..38979c2 100644 --- a/Screenshots/review-npc-prefab.png +++ b/Screenshots/review-npc-prefab.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99501ac2edfa22a64a7156fbc298930db64997008d7534189f0ee9a208b9506a -size 1735511 +oid sha256:c8e3664310a7e70ff816b8e677f2d6c4ce2a31bb8e0f07e048f9df3467be4de0 +size 1737260 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index 0da3826..2b947d0 100644 --- a/Screenshots/review-overview.png +++ b/Screenshots/review-overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f40eda489a19e01f3e0e6428940e4da7ee6865c7397e4e9bbe64f6e7a84b4af0 -size 2488592 +oid sha256:c028e5e01bf9d46f33b7a903f50923ccaee537112d9bb519bb4376766ca6beff +size 2619516 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index 102d17f..8b45239 100644 --- a/Screenshots/review-player-placement.png +++ b/Screenshots/review-player-placement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f66e9a9e157c4663b971d15dcdfc8d3b67d64584c9f92a0d5ff207aac7637d37 -size 2019699 +oid sha256:a4a003f484e9e048c1c73f6d5bfac87c1db332b1aae02eddf7f7c913af098a9a +size 2027282 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 3d27366..0ce7ec0 100644 --- a/Screenshots/review-scale-cubes.png +++ b/Screenshots/review-scale-cubes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be833e2571fa2311499bbd806d32984c4a0b7bcaa33af2ff362fc6c94f553bff -size 1937905 +oid sha256:f8026dd45a8fdb6580c3a0e82aad50518a1d0b27fc9a2a93e419924df651c72e +size 1938060 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index 3888105..1b0cd7a 100644 --- a/Screenshots/review-shadow-compare.png +++ b/Screenshots/review-shadow-compare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75c80b08b7905b9e6c9df673151c2b0d11362c3a433b402e22240b66c647181b -size 1726246 +oid sha256:dcb80cdfaca40ab3ec68df835e1c31aa66ac8f24bea0eb44e928946b2a9b378d +size 1752868 diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index c7d907a..4dcd7bb 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -65,6 +65,13 @@ def ensure_material(mat_name, spec): spec keys: dark, bright (RGB tuples), roughness, specular, noise_scale (world units per noise cycle; smaller = larger features). + + Optional `courses_m` adds **masonry coursing**: horizontal joint lines at a + given course height in world metres, plus a per-course horizontal offset so + the vertical joints stagger like real ashlar. Optional `joint_tint` is the + colour the joints darken toward, and `roughness_variation` lerps roughness + between `roughness` and a rougher value using the same noise, so the surface + does not read as one flat specular value. """ path = f"/Game/Hollywood/Site/{mat_name}" if unreal.EditorAssetLibrary.does_asset_exist(path): @@ -88,6 +95,20 @@ def ensure_material(mat_name, spec): n.set_editor_property("r", float(value)) return n + def link(src, dst, pin=""): + """Connect, and say so when it fails. + + `connect_material_expressions` returns False rather than raising, so an + unknown pin name silently leaves an input unconnected and the material + compiles with a default value — a coursing mask of 0 is invisible, which + is exactly how the first attempt failed. + """ + ok = lib.connect_material_expressions(src, "", dst, pin) + if not ok: + log(f"material {mat_name}: FAILED to connect {src.__class__.__name__} " + f"-> {dst.__class__.__name__}.{pin or '(default)'}") + return ok + # world position -> noise -> lerp(dark, bright) -> base colour wp = node(unreal.MaterialExpressionWorldPosition) scale = scalar(spec.get("noise_scale", 0.004)) @@ -108,10 +129,57 @@ def ensure_material(mat_name, spec): lib.connect_material_expressions(c3(spec["dark"]), "", lerp, "A") lib.connect_material_expressions(c3(spec["bright"]), "", lerp, "B") lib.connect_material_expressions(noise, "", lerp, "Alpha") - lib.connect_material_property(lerp, "", unreal.MaterialProperty.MP_BASE_COLOR) - lib.connect_material_property( - scalar(spec.get("roughness", 0.85)), "", unreal.MaterialProperty.MP_ROUGHNESS) + # --- optional masonry coursing ----------------------------------------- + # A world-space course mask: the fractional part of Z / course height is a + # sawtooth per course; zeroing it gives a joint line every course. The row + # index shifts each course sideways by half a brick, so vertical joints + # stagger. All of it is world-space, so every building shares the coursing + # without needing its own UVs. + base_color_expr = lerp + if spec.get("courses_m"): + # WorldPosition is in CENTIMETRES, so a course height given in metres must + # be scaled by 100 here; getting this wrong makes the coursing 100x too + # fine to see at all (the first attempt did exactly that). + course_cm = float(spec["courses_m"]) * 100.0 + row = node(unreal.MaterialExpressionDivide) + link(wp, row, "A") + link(c3((course_cm, course_cm, course_cm)), row, "B") + saw = node(unreal.MaterialExpressionFrac) + link(row, saw) + # Joint mask without a comparison node (UE has no Less): 1 - saw/joint is + # 1 at the course boundary and 0 once past the joint width; clamped to 0..1 + # so it is a linear band rather than a hard step. + div = node(unreal.MaterialExpressionDivide) + link(saw, div, "A") + # joint_width is a fraction of one course (0.11 ~= a 6 cm joint on a + # 55 cm course), which is the space `saw` lives in after the divide. + link(scalar(spec.get("joint_width", 0.11)), div, "B") + inv = node(unreal.MaterialExpressionOneMinus) + link(div, inv) + joint = node(unreal.MaterialExpressionClamp) + link(inv, joint) + tinted = node(unreal.MaterialExpressionLinearInterpolate) + link(lerp, tinted, "A") + link(c3(spec.get("joint_tint", (0.34, 0.31, 0.27))), tinted, "B") + link(joint, tinted, "Alpha") + base_color_expr = tinted + + lib.connect_material_property(base_color_expr, "", unreal.MaterialProperty.MP_BASE_COLOR) + + # Roughness: lerp(base, base + variation, noise) so the highlight breaks up. + variation = float(spec.get("roughness_variation", 0.0)) + if variation > 0.0: + rough_lerp = node(unreal.MaterialExpressionLinearInterpolate) + lib.connect_material_expressions( + scalar(spec.get("roughness", 0.85)), "", rough_lerp, "A") + lib.connect_material_expressions( + scalar(min(1.0, spec.get("roughness", 0.85) + variation)), "", rough_lerp, "B") + lib.connect_material_expressions(noise, "", rough_lerp, "Alpha") + lib.connect_material_property(rough_lerp, "", unreal.MaterialProperty.MP_ROUGHNESS) + else: + lib.connect_material_property( + scalar(spec.get("roughness", 0.85)), "", unreal.MaterialProperty.MP_ROUGHNESS) try: lib.connect_material_property( scalar(spec.get("specular", 0.3)), "", unreal.MaterialProperty.MP_SPECULAR) @@ -278,10 +346,35 @@ def build(): roughness=0.98, specular=0.15, noise_scale=0.003), "M_RoadPaint": dict(dark=(0.74, 0.74, 0.72), bright=(0.86, 0.86, 0.84), roughness=0.62, specular=0.35, noise_scale=0.02), - "M_FacadeStone": dict(dark=(0.62, 0.58, 0.50), bright=(0.74, 0.70, 0.62), - roughness=0.72, specular=0.32, noise_scale=0.005), - "M_Stucco": dict(dark=(0.52, 0.48, 0.42), bright=(0.66, 0.62, 0.55), - roughness=0.86, specular=0.25, noise_scale=0.004), + # Ashlar coursing at 0.55 m (a 0.10 m band joint), the reading the 2009 + # facade photograph gives for the hero's stone; roughness breaks up so the + # wall is not one specular plane. + "M_FacadeStone": dict(dark=(0.62, 0.58, 0.50), bright=(0.76, 0.72, 0.64), + roughness=0.70, specular=0.32, noise_scale=0.005, + courses_m=0.55, joint_width=0.11, + joint_tint=(0.40, 0.37, 0.32), roughness_variation=0.12), + # Rendered/stucco frontage: no coursing, but a fine tooth and a wider + # roughness range than before, because flat stucco is what read as plastic. + "M_Stucco": dict(dark=(0.50, 0.46, 0.40), bright=(0.68, 0.64, 0.57), + roughness=0.76, specular=0.25, noise_scale=0.0025, + roughness_variation=0.20), + # Stone in shadow: the hero's recessed wall behind the colonnade. Same + # coursing as the face, a stop darker, so depth reads without a light. + "M_FacadeStoneShadow": dict(dark=(0.40, 0.37, 0.32), bright=(0.52, 0.49, 0.43), + roughness=0.78, specular=0.26, noise_scale=0.005, + courses_m=0.55, joint_width=0.11, + joint_tint=(0.28, 0.26, 0.23), + roughness_variation=0.10), + # Glazing: low roughness, strong specular. A plausible IOR for glass sits + # near 1.5, which UE expresses through specular ~0.5-0.6 on an opaque + # surface (there is no refraction pass at blockout fidelity). + "M_FacadeGlass": dict(dark=(0.030, 0.045, 0.055), bright=(0.075, 0.095, 0.110), + roughness=0.14, specular=0.62, noise_scale=0.01, + roughness_variation=0.06), + # The JIMMY KIMMEL banners: the one saturated surface on the elevation. + "M_FacadeBanner": dict(dark=(0.32, 0.05, 0.07), bright=(0.55, 0.10, 0.12), + roughness=0.72, specular=0.22, noise_scale=0.02, + roughness_variation=0.12), } SURFACE_MATERIALS = { "Terrain_DEM": "M_Ground", @@ -337,15 +430,45 @@ def build(): LABEL_MATERIALS = {"Hero_Facade": "M_FacadeStone", "North_TCL_Chinese": "M_Stucco", "North_Madame_Tussauds": "M_Stucco"} + + # Assign per SLOT, by what the slot's imported material is called, not just + # slot 0: the hero mesh's slot 0 is the banner, so the stone, the shadowed + # stone and the glazing were all still wearing their flat Blender materials. + SLOT_RULES = ( + ("glass", "M_FacadeGlass"), + ("banner", "M_FacadeBanner"), + ("stoneshadow", "M_FacadeStoneShadow"), + ("stone", "M_FacadeStone"), + ("roof", "M_Concrete"), + ("wall", "M_Stucco"), + ) + + def slot_material_name(slot_mesh_material): + if not slot_mesh_material: + return None + key = slot_mesh_material.get_name().lower() + for needle, target in SLOT_RULES: + if needle in key: + return target + return None + assigned = 0 for actor in unreal.EditorLevelLibrary.get_all_level_actors(): - mat_name = LABEL_MATERIALS.get(actor.get_actor_label()) - if not mat_name or mat_name not in built: - continue + fallback = LABEL_MATERIALS.get(actor.get_actor_label()) comp = actor.get_component_by_class(unreal.StaticMeshComponent) - if comp and comp.static_mesh: - comp.set_material(0, built[mat_name]) + if not comp or not comp.static_mesh: + continue + slots = comp.static_mesh.get_editor_property("static_materials") + touched = 0 + for index, slot in enumerate(slots): + mesh_mat = slot.get_editor_property("material_interface") + target = slot_material_name(mesh_mat) or fallback + if target and target in built: + comp.set_material(index, built[target]) + touched += 1 + if touched: assigned += 1 + log(f"materials {actor.get_actor_label()}: {touched}/{len(slots)} slots") log(f"assigned look materials to {assigned} facade/background actors") # --- surrounding block frontage --------------------------------------- diff --git a/Tools/ue_build_review_map.py b/Tools/ue_build_review_map.py index 583c037..cf570bc 100644 --- a/Tools/ue_build_review_map.py +++ b/Tools/ue_build_review_map.py @@ -240,9 +240,9 @@ try: # verticals stay parallel. The old shot looked up ~4 deg from 4.2 m at a # 7.6 m target, which converged the columns — the one thing an # architectural elevation must not do (archviz: "straight verticals"). - ("hero-facade-front", unreal.Vector(hero_x + 200, -3200, 760), unreal.Vector(hero_x, hero_y + 400, 760)), + ("hero-facade-front", unreal.Vector(hero_x + 200, -3200, 760), unreal.Vector(hero_x, hero_y + 400, 760), 42.0), # oblique from the north-west, reading the colonnade in depth - ("hero-facade-oblique", unreal.Vector(hero_x - 2200, -3400, 620), unreal.Vector(hero_x + 300, hero_y, 700)), + ("hero-facade-oblique", unreal.Vector(hero_x - 2200, -3400, 620), unreal.Vector(hero_x + 300, hero_y, 700), 55.0), ] # --- review shot cameras + save ------------------------------------------- # Order matters: the review scene is staged in the default (unsaved) world. @@ -256,6 +256,12 @@ try: "shot_rotations", [unreal.MathLibrary.find_look_at_rotation(c[1], c[2]) for c in cams]) shots.set_editor_property("shot_names", [c[0] for c in cams]) + # Optional 4th element: field of view in degrees. 0 keeps the default 90. + # Elevations shot from across the street use a long lens so the building + # fills the frame without pitching the camera (archviz: straight + # verticals, telephoto for elevations). + shots.set_editor_property( + "shot_field_of_view", [float(c[3]) if len(c) > 3 else 0.0 for c in cams]) shots.set_editor_property("quit_when_done", True) log(f"staged: ShotSequencer with {len(cams)} cameras: {', '.join(c[0] for c in cams)}") @@ -282,7 +288,7 @@ try: # the colonnade's underside. # Level horizon for the same reason: a block elevation is read by its # verticals, and a pitched camera makes them lean. - ("demo-spawn-view", unreal.Vector(-13336, -3100, 620), unreal.Vector(-13336, 1300, 620)), + ("demo-spawn-view", unreal.Vector(-13336, -3100, 620), unreal.Vector(-13336, 1300, 620), 48.0), # Street level, from the north sidewalk: the old camera sat at Y -400, # inside the facade body (which spans Y -1457..-200), so it looked at # the unlit back face. Y -2600 is on the sidewalk in front of the lit @@ -309,6 +315,9 @@ try: seq.set_editor_property("shot_rotations", [unreal.MathLibrary.find_look_at_rotation(c[1], c[2]) for c in demo_shots]) seq.set_editor_property("shot_names", [c[0] for c in demo_shots]) + seq.set_editor_property( + "shot_field_of_view", + [float(c[3]) if len(c) > 3 else 0.0 for c in demo_shots]) # quit after the demo shots so a dedicated DemoMap capture run # terminates cleanly (render_screenshots.sh runs both maps). seq.set_editor_property("quit_when_done", True) -- 2.47.3 From 0f928531df68c5dc1b08d207925c2be56a5da11d Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:00:45 +0100 Subject: [PATCH 02/13] todo: record the 0.1.4 materials progress and what remains --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 33ea0c1..4388110 100644 --- a/TODO.md +++ b/TODO.md @@ -13,11 +13,11 @@ Actionable task list for this project. This file is the single, central place fo Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the façade module kit — bay, pilaster, cornice, parapet, window, shopfront, door — as reusable node groups), **archviz** (real-world scale and façade composition), **materials** (procedural brick, concrete, stone, render, glass — the ported generators), **texture-workflow** (AO/curvature/normal bakes and decals for the motto band and medallions), **uv-workflow** (texel density and modular UV layouts for the kit), **scene-assembly** (assembling kit pieces onto the fifteen block buildings), **lookdev** (screenshot-comparison loop until shading reads correctly), **historical-worlds** or **realistic-style** (1920s Hollywood Blvd neoclassical direction), **asset-optimization** + **unreal-export** (pre-export validation, `ue_metric_v1` preset), **qa-review** (final gate before the `Tools/render_screenshots.sh` review). -### Patch tasks +### Patch tasks - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `facades-0.1.4`) - [ ] **Finish the Blosm blockout cleaning/detailing (moved from the deferred `0.1.0` item).** Import state: `Art/blender/blockout/blockout-2026-11-06.blend` (86 meshes, EPSG:26911, 0.0 cm affine residual) with cleaning tooling in `Geo/tools/` and `Art/blender/blockout/blosm_blockout.py`. To do here: (a) untextured roofs/facades → **Replace placeholder materials** below; (b) Blosm `setupScript` façade styles → **Give OSM buildings a façade style** below; (c) the 3 of 15 buildings lacking height data are the `0.1.2` placeholder-heights item. - [ ] **Define the façade module.** One set of reusable pieces (bay, pilaster, cornice, parapet, window, shopfront, door) at a fixed module, exported with the `ue_metric_v1` preset so every building assembles from the same kit. -- [ ] **Replace placeholder materials.** Every blockout building currently carries Blender flat colours. Port the procedural generators from `Tools/sibling_asset_audit.py`'s sibling project (`brick_facade`, `seamless_concrete`, `asphalt`) and add stone, render, and glass, then wire them through Unreal material instances. +- [A] **Replace placeholder materials.** **In progress 2026-11-09.** `ensure_material()` now builds masonry coursing (world-space course height + joint band + tint, corrected for UE's centimetre WorldPosition) and roughness variation; new `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5 without a refraction pass) and `M_FacadeBanner`; assignment is per **slot** keyed on the imported slot name (it was slot-0 only, which on the hero is the banner, so stone/glass/shadow were still flat). Hero: 4/4 slots, frontage and north buildings 2/2. Remaining: bake real texture maps (brick/stone/asphalt generators) rather than graph-only detail, and give the frontage shopfront line its own material. - [ ] **Give OSM buildings a façade style.** Blosm exposes `scene.blosm.setupScript` (`setup/neoclassical.py` is the example) so a building can carry a real elevation instead of a plain extrusion. Build one style script for the south frontage and one for the north. - [ ] **Hero façade detail pass.** Ionic capitals and fluting, the parapet motto band and medallions as geometry or decal, window rhythm behind the colonnade, and the three portal flights at their photographed proportions. - [ ] **El Capitan Theatre (6838) and the retail neighbours.** Model from the 18-frame 5472×3648 reference set; the theatre shares the measured 4.96 m bay rhythm with the temple. -- 2.47.3 From 73e535a24b31114efe7bf5bc513bc28bff3bde68 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:04:03 +0100 Subject: [PATCH 03/13] feat(materials): generated facade texture maps, sampled world-aligned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Tools/generate_facade_textures.py: deterministic, stdlib-only PNG generator for four facade surface sets (ashlar stone, stucco, asphalt, concrete), each as base colour (_BC) plus roughness (_R). Pure zlib encoder and hash-based value noise, so re-running produces byte-identical files (verified by md5sum); ~14 s for eight 512² maps. ue_build_demo_map.py imports them into /Game/Hollywood/Textures and samples them world-aligned: WorldPosition (cm) * 1/tile_cm -> ComponentMask(R,G) -> TextureSample UVs, with per-material tile sizes (stone 440, stucco 260, asphalt 900, concrete 250 cm). A textured material skips the graph coursing deliberately - overlaying both would double the course pattern. Textures imported 8/8, hero 4/4 slots assigned, map saved; screenshots re-rendered. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.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 | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 3 + .../Hollywood/Textures/T_Asphalt_R.uasset | 3 + .../Hollywood/Textures/T_Concrete_BC.uasset | 3 + .../Hollywood/Textures/T_Concrete_R.uasset | 3 + .../Textures/T_StoneAshlar_BC.uasset | 3 + .../Hollywood/Textures/T_StoneAshlar_R.uasset | 3 + .../Hollywood/Textures/T_Stucco_BC.uasset | 3 + .../Hollywood/Textures/T_Stucco_R.uasset | 3 + Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 18 ++ Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- Screenshots/review-character.png | 4 +- Screenshots/review-ground-contact.png | 4 +- Screenshots/review-hero-facade-front.png | 4 +- Screenshots/review-hero-facade-oblique.png | 4 +- Screenshots/review-hero-portals.png | 4 +- Screenshots/review-npc-prefab.png | 4 +- Screenshots/review-overview.png | 4 +- Screenshots/review-player-placement.png | 4 +- Screenshots/review-scale-cubes.png | 4 +- Screenshots/review-shadow-compare.png | 4 +- Tools/README.md | 1 + Tools/generate_facade_textures.py | 226 ++++++++++++++++++ Tools/ue_build_demo_map.py | 96 +++++++- 39 files changed, 401 insertions(+), 48 deletions(-) create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset create mode 100644 Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset create mode 100644 Tools/generate_facade_textures.py diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 21ed7b4..34b7b1e 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:5544cf164533ce61534d98075bbf41e6fcaa91f340492c82ce10154a75028013 -size 55058 +oid sha256:0dc9468e94f868cd76893dd1d8e6cd2ef0b365ce3fa86f3feed5f65a630af9cb +size 55066 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 14f0f6c..d83bc87 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:8f56c5b314d6f7844f2833beefe709676368ccbd2a45ae30f48ec0ae83da4af2 +oid sha256:29dcab210b352dbcec4665b5d18e03080327938ea2be01dfe0c36862c8161095 size 7876 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 2cc2114..909a78f 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:2471e65a5e1809aa78abdb6e52de01e72a426ed9c825c2d90e7a0f8bf1dda400 +oid sha256:14c5b272b2a67088a123541518fa5749e47e2dc994b51ae699fd8fc114aee3ad size 7881 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index 44e5528..8eb662b 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:832e10baec763e12727cd6be226ea1fb9ced63aadaf55f25cd5323a7a8fec8c4 +oid sha256:158ac3fd592d71816fe061cac6aa23a025bb3a8f83ca338bc510e144bc4a06dd size 8667 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index dd46ad6..93e7535 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be4d972ad3655d1c9ec383b69f66c7ead7c8279e9425dfc442dfd7c7393e78a8 +oid sha256:cb21c5ac661880794989401129b2ea4d424c6a19dc5072b1505f778709d562bd size 8662 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 0e63bee..0f7c2de 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:bf20102e53609e517647200ad44528284dbc5bd26e00d1b41467e3db2ca3c6f1 +oid sha256:643078c1b3aef3b980d208bc4bc9cdc727b337a6e00ac58460a4b601476e9019 size 12131 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index f1d76f5..00bdaa8 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8876871cf45828b2da703e3082bc3bb5a2fb63263958ca5314b4072fe7418ec1 +oid sha256:bb32d2e8dc8f85f2f1f6bde81faa7742a9c9b3f2f5b5c9adc66a96be43a1fc69 size 12161 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 3317c51..0f0755d 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:adb033875c629572815f0d27b94a16bdc3b869344419ad7343b59c8ccdc1e850 +oid sha256:b22a46ee7729364ba83d6cac7b38634fa32d93140c29b2003f73f7bf2a2c0aac size 7871 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index 1becb71..ab8e270 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:715ec7945cf7eb69f07755252ccc4b39ea1cbfad1a55eddb99616517ef5def42 +oid sha256:a06a0150737402a11bf660ff6848564975da950654eacac7023b3932be2e962f size 7901 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 91ddd02..e333504 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:dcfda0b94f7141564391069629682cac71c9152100038a5e927a2355c89c869a +oid sha256:b25e55f9bfb3181e8f12535b43ce829d3923668bbab3635ec4c321534fd76305 size 7886 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 9f20fe3..00df7ae 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:940807fd433a5eab6726c830b4c54874a4acf3c352e415e6de71f0f6ed1b4ede +oid sha256:df485df5c559d21b96682f2a381a6a53c9456c5d14b34394e2b5f0e93cbf04b8 size 8637 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 1dc6b23..d60380d 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:a7b7c866693db2f80334baeb66af3ff2585368a503af806c3d78cae71f328b17 +oid sha256:69075d0ba49303ed0c25fbbc966abc7b0d2f3fe34d716bf648989e04394da70d size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset new file mode 100644 index 0000000..8862664 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa06702c3b89e8c16522f94292bc69e01ffe11e92d050bdee27341431774fa21 +size 70786 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset new file mode 100644 index 0000000..b5e0223 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90efc297644eae3bb067300cef611efe68e48e25960a259fa482e717d77a35ad +size 28917 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset new file mode 100644 index 0000000..88ae661 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8f007448f3038bbac6bb70a77d26a4bc8ddccd02a227cedba74d14d901c631a +size 58474 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset new file mode 100644 index 0000000..3ba2425 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcc36e012cb5428e1568976cdf440be179330e635e09c2ce5b78d3d71dea67a4 +size 17423 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset new file mode 100644 index 0000000..b3fae28 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a1b2cac653493aec57f1878f81ffe823dea291c2a6ea27e1e78c3fa3aed2359 +size 40517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset new file mode 100644 index 0000000..491e0a6 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0613545632883b4256025b712c35a8c9345c364beeb0089592163a1a84973f69 +size 18677 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset new file mode 100644 index 0000000..198d125 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6edac9a08e27459933560127a922e504f18b88656e37932699fc18e8920979f6 +size 62653 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset new file mode 100644 index 0000000..8639638 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:089a9df31bce521e14f6519539063ac1c5eb73641a73c70189aad9725cd4eefd +size 17448 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 14c9200..bd50cc5 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bef39374cc710661ddf2f15194d28b298bfa0d0adcf25668740712381c2ba29 +oid sha256:06db40a8beab60daaf548554fc70b85eb4cd4ab1f8371b0e8ccd4b7e33c1648d size 28811 diff --git a/MEMORY.md b/MEMORY.md index 1006f57..5834e6d 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -550,3 +550,21 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. a 4th element on the shot tuples; a long lens (42–55°) fills the frame from across the street while the camera stays level, so verticals stay parallel. `review-hero-facade-front` sky 39 → 20 %, `demo-spawn-view` 23 → 16 %. + +## 2026-11-09 — facade texture maps (0.1.4) + +- `Tools/generate_facade_textures.py` writes deterministic 512² base-colour and + roughness maps (ashlar stone, stucco, asphalt, concrete) with a pure-zlib PNG + encoder and hash-based value noise — no numpy/Pillow, ~14 s for all eight, and + byte-identical on a re-run (checked by md5sum). +- `ue_build_demo_map.py` imports them to `/Game/Hollywood/Textures` and samples + them **world-aligned**: WorldPosition (cm) → ×(1/tile_cm) → ComponentMask(R,G) + → TextureSample UVs. `tile_cm` per material: stone 440, stucco 260, asphalt 900, + concrete 250. +- Gotchas: `MaterialExpressionTextureSample`'s UV input pin is **"UVs"**, and the + output pin for colour is **"RGB"** / roughness **"R"**. `ComponentMask` pins are + R/G/B/A booleans. A textured material skips the graph coursing on purpose — + overlaying both would double the course pattern. +- The script needs `ROOT` in `ue_build_demo_map.py` and `sys` imported; forgetting + either fails with `NameError` and an exit code of 0 from the commandlet (always + read the result file under /tmp/gigline). diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 4476642..ce2ce2d 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a21bd2e7d32099ee8490de63551e817e144ce74cade34dbf7854bfc125a899d0 -size 2756050 +oid sha256:7b1aeaed19783379067f81e67c4286bb28017c50c4fa713574ac18370c3846bb +size 2755870 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 83da020..1c4503e 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f93538fc3aa3d3b0a83c5b0ba9637445ef66a2fba845e0dd3f39fc04ddac06ac -size 2265651 +oid sha256:0d83115402d0a44b8b025dc8ba35505bba8fc1410ead6f830df8c819d3020e79 +size 2267060 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 4a928e0..0eaeefd 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4a781b779e59facdcd0275cc147ea4f9a5cf9f36c9061c400eca79f69aceb2d -size 2843274 +oid sha256:701203ac6afe5d7897e32414bce1bc09dc85cfd747636104ab5b3cad48306d47 +size 2838039 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 2da3f96..fc88e69 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:744bb6b3ddcf3130e11d93c1ba6eb0b71e5968629d2517401cd36ecc74e020c8 -size 2704752 +oid sha256:2607aaa8e9333dd033745ebbac6b99b31641049e03982d66d5d1a0b1b086de9d +size 2703487 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index d1c2dc1..de5a517 100644 --- a/Screenshots/review-character.png +++ b/Screenshots/review-character.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e794bb187f5f1cd27cfcdf89a504ce22a33a529cc3244ba5381af930c33e778d -size 2539235 +oid sha256:2d38dc63e0c09483ec15aaa493b483baa8d6d7d7a2678415fef5519561226ebd +size 2540302 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index 7c5785b..113c7d5 100644 --- a/Screenshots/review-ground-contact.png +++ b/Screenshots/review-ground-contact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:586c717e6066aed87c6f7caf1f566789c5ecf17cecf786cd7894a8e4d75afeb8 -size 1756397 +oid sha256:2797ca6c909c3956795361fcef86dfa8268007ab3df23adf07be807d0f0e073f +size 1750724 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index ccb004b..d290e35 100644 --- a/Screenshots/review-hero-facade-front.png +++ b/Screenshots/review-hero-facade-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6ad9425e1cda90135358752c655a70a08e0d449629d1f5075b31fcc272eb601 -size 2297053 +oid sha256:f428e43ac9117e9e45a662a89d82f5b636bd8eebd07cad5de8c8c3894a146b09 +size 2298703 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index 410d1ff..d603bef 100644 --- a/Screenshots/review-hero-facade-oblique.png +++ b/Screenshots/review-hero-facade-oblique.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9731498fc8054b94de923cb311f38c31be95af642786bf64f6015db357823559 -size 1516067 +oid sha256:ae882d2fb120ce45c4bed16c4e5288400bbbe9e565a93f29768e353ebb280870 +size 1515587 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index 4f8adb4..b73d080 100644 --- a/Screenshots/review-hero-portals.png +++ b/Screenshots/review-hero-portals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d02821b21a4b61fed206bb5a8f38ff3b1ab27a0cc16e26cfd0639eb1f5a32ab -size 1327638 +oid sha256:795bffa54e8efc0bb3aafb47a3cfa6282ea41c3a55a0402fb349c2e7ddbedd53 +size 1321845 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index 38979c2..f0c8c75 100644 --- a/Screenshots/review-npc-prefab.png +++ b/Screenshots/review-npc-prefab.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8e3664310a7e70ff816b8e677f2d6c4ce2a31bb8e0f07e048f9df3467be4de0 -size 1737260 +oid sha256:12ba9d44bb231629abf2e0a607d22e1c7c940b953b56c089efb8810d8ffce88a +size 1736896 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index 2b947d0..0a51b2a 100644 --- a/Screenshots/review-overview.png +++ b/Screenshots/review-overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c028e5e01bf9d46f33b7a903f50923ccaee537112d9bb519bb4376766ca6beff -size 2619516 +oid sha256:1d3ccc49c65e6347e273d07fe7cf72839ad3b4b74c37ab9e4e1d92f4020b4390 +size 2614061 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index 8b45239..c80f9f1 100644 --- a/Screenshots/review-player-placement.png +++ b/Screenshots/review-player-placement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4a003f484e9e048c1c73f6d5bfac87c1db332b1aae02eddf7f7c913af098a9a -size 2027282 +oid sha256:e8ad82386c9504b3e2bdd461c22c57e2e9cb19762aaf794f47dd8472ff01f436 +size 2025973 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 0ce7ec0..71f4bef 100644 --- a/Screenshots/review-scale-cubes.png +++ b/Screenshots/review-scale-cubes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8026dd45a8fdb6580c3a0e82aad50518a1d0b27fc9a2a93e419924df651c72e -size 1938060 +oid sha256:3aa35281f616ad86f1d7b2de6a79b0bf3fd476b8c259dc3d3449c6ef7f3dc960 +size 1942628 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index 1b0cd7a..aa480e0 100644 --- a/Screenshots/review-shadow-compare.png +++ b/Screenshots/review-shadow-compare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcb80cdfaca40ab3ec68df835e1c31aa66ac8f24bea0eb44e928946b2a9b378d -size 1752868 +oid sha256:b7a03d6be117470d86bd351aa35584350140bcf6ff2aae3b35da79e062672a4c +size 1723008 diff --git a/Tools/README.md b/Tools/README.md index 988b14a..f54bb8d 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -16,6 +16,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | Tool | Purpose | | --- | --- | | `check-lfs.sh` | Fails if the `lfs` filter is missing or an LFS-tracked file was committed as a plain blob | +| `generate_facade_textures.py` | Deterministic stdlib PNG generator for the facade maps (`T_StoneAshlar`, `T_Stucco`, `T_Asphalt`, `T_Concrete`, each `_BC` base colour + `_R` roughness). Pure-zlib writer, hash-based value noise, identical bytes on every run; `--size` and `--out` (`/tmp/gigline/textures` by default). `ue_build_demo_map.py` imports them into `/Game/Hollywood/Textures` and samples them world-aligned. | | `ue_common.py` | Shared helpers for the Unreal commandlet scripts: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False silently), and `require_saved()` makes that failure explicit. | | `fbx_export_preset.py` | The **single** FBX export preset (`ue_metric_v1` / `ue_character_v1`): metres→centimetres via `FBX_SCALE_NONE`, axis mapping east +X / north −Y / up +Z, `mesh_smooth_type="FACE"` (smoothing groups survive the import). Every Blender exporter imports it instead of copying the argument block; `preset_kwargs()` is bpy-free and unit-tested. | | `sibling_asset_audit.py` | Reads the sibling project's prop library from GLB headers into `Art/props/reuse-manifest.json`; copies nothing | diff --git a/Tools/generate_facade_textures.py b/Tools/generate_facade_textures.py new file mode 100644 index 0000000..f42488a --- /dev/null +++ b/Tools/generate_facade_textures.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +"""Generate the facade texture maps the demo materials sample. + +The materials started as procedural graphs only (noise + coursing), which reads +as tinted stone but has no grain. These maps add the grain: an ashlar stone +course pattern, a rendered stucco tooth, asphalt aggregate and concrete pores, +each as base colour plus a roughness map so the specular breaks up. + +Standard library only — the PNG encoder is the same pure-zlib writer used for the +MPFB skin, so this runs on the host without numpy or Pillow. + + python3 Tools/generate_facade_textures.py # write to /tmp/gigline/textures + python3 Tools/generate_facade_textures.py --size 1024 + python3 Tools/generate_facade_textures.py --out Art/blender/textures + +Output names match the Unreal import destination `/Game/Hollywood/Textures`: + + T_StoneAshlar_BC.png T_StoneAshlar_R.png + T_Stucco_BC.png T_Stucco_R.png + T_Asphalt_BC.png T_Asphalt_R.png + T_Concrete_BC.png T_Concrete_R.png + +Determinism: every pattern is a deterministic hash of the pixel coordinate, so a +re-run reproduces the same files byte for byte (no RNG state, no seam drift). +""" + +from __future__ import annotations + +import argparse +import math +import os +import struct +import zlib + +# --- PNG ------------------------------------------------------------------- + + +def write_png(path, width, height, rgb_rows): + """Write 8-bit RGB rows (bytes) to a PNG. `rgb_rows` is one bytes per row.""" + raw = bytearray() + for y in range(height): + raw.append(0) # filter type 0 + raw += rgb_rows[y] + + def chunk(tag, data): + c = struct.pack(">I", len(data)) + tag + data + c += struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) + return c + + ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0) # colour type 2 = RGB + png = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + png += chunk(b"IDAT", zlib.compress(bytes(raw), 6)) + chunk(b"IEND", b"") + os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True) + with open(path, "wb") as handle: + handle.write(png) + return path + + +# --- deterministic value noise -------------------------------------------- + + +def _hash2(x, y, seed=0): + """Deterministic 0..1 hash of an integer cell. No RNG, fully reproducible.""" + n = (x * 374761393 + y * 668265263 + seed * 1442695040888963407) & 0xFFFFFFFF + n = (n ^ (n >> 13)) * 1274126177 & 0xFFFFFFFF + return ((n ^ (n >> 16)) & 0xFFFF) / 65535.0 + + +def value_noise(x, y, seed=0): + """Smooth bilinear value noise at continuous (x, y) in cell units.""" + xi, yi = math.floor(x), math.floor(y) + fx, fy = x - xi, y - yi + # smoothstep weights so cells blend without visible lattice lines + sx = fx * fx * (3.0 - 2.0 * fx) + sy = fy * fy * (3.0 - 2.0 * fy) + n00 = _hash2(xi, yi, seed) + n10 = _hash2(xi + 1, yi, seed) + n01 = _hash2(xi, yi + 1, seed) + n11 = _hash2(xi + 1, yi + 1, seed) + return (n00 * (1 - sx) + n10 * sx) * (1 - sy) + (n01 * (1 - sx) + n11 * sx) * sy + + +def fractal(x, y, octaves=4, seed=0): + """Fractal sum of value noise, normalised to 0..1.""" + total, amp, freq, norm = 0.0, 1.0, 1.0, 0.0 + for _ in range(octaves): + total += value_noise(x * freq, y * freq, seed) * amp + norm += amp + amp *= 0.5 + freq *= 2.0 + return total / norm + + +def clamp8(value): + return max(0, min(255, int(value * 255.0 + 0.5))) + + +# --- patterns -------------------------------------------------------------- +# Each returns (base_rgb_function, roughness_function) over u, v in 0..1. + + +def stone_ashlar(courses=8, per_course=4): + """Coursed ashlar: horizontal beds, staggered vertical joints, stone grain.""" + course_h = 1.0 / courses + + def block_uv(u, v): + row = int(v / course_h) + shift = 0.5 * (row % 2) / per_course # stagger alternate courses + col = int(((u + shift) % 1.0) * per_course) + return row, col, (v % course_h) / course_h, ((u + shift) % 1.0) * per_course - col + + def base(u, v, px, py): + row, col, fj, fi = block_uv(u, v) + tone = 0.62 + 0.10 * _hash2(row, col, 11) # each block a little different + grain = 0.06 * (fractal(px * 0.15, py * 0.15, 5, 3) - 0.5) * 2.0 + # joint: a dark band along the bed and the head of each block + joint = 0.0 + if fj < 0.055 or fi < 0.055: + joint = -0.22 + value = tone + grain + joint + return (value * 1.02, value, value * 0.94) # faintly warm stone + + def rough(u, v, px, py): + row, col, fj, fi = block_uv(u, v) + base_r = 0.66 + 0.10 * _hash2(row, col, 29) + if fj < 0.055 or fi < 0.055: + base_r = 0.86 # joints are matte + return base_r + 0.06 * fractal(px * 0.2, py * 0.2, 3, 7) + + return base, rough + + +def stucco(): + def base(u, v, px, py): + blot = fractal(px * 0.03, py * 0.03, 4, 21) + tooth = fractal(px * 0.9, py * 0.9, 3, 22) + value = 0.58 + 0.12 * (blot - 0.5) + 0.05 * (tooth - 0.5) + return (value * 1.03, value, value * 0.96) + + def rough(u, v, px, py): + return 0.74 + 0.18 * fractal(px * 0.06, py * 0.06, 3, 31) + + return base, rough + + +def asphalt(): + def base(u, v, px, py): + agg = fractal(px * 1.4, py * 1.4, 3, 41) # fine aggregate + patch = fractal(px * 0.02, py * 0.02, 3, 42) # repair patches + value = 0.055 + 0.05 * (patch - 0.5) + 0.06 * (agg - 0.5) + return (value, value, value * 1.05) # cool bitumen + + def rough(u, v, px, py): + return 0.90 + 0.08 * fractal(px * 0.5, py * 0.5, 2, 43) + + return base, rough + + +def concrete(): + def base(u, v, px, py): + pores = fractal(px * 0.8, py * 0.8, 3, 51) + tone = fractal(px * 0.04, py * 0.04, 4, 52) + value = 0.50 + 0.10 * (tone - 0.5) + 0.05 * (pores - 0.5) + return (value * 1.01, value, value * 0.97) + + def rough(u, v, px, py): + return 0.84 + 0.10 * fractal(px * 0.15, py * 0.15, 3, 53) + + return base, rough + + +TEXTURES = { + "T_StoneAshlar": stone_ashlar(), + "T_Stucco": stucco(), + "T_Asphalt": asphalt(), + "T_Concrete": concrete(), +} + + +def render(name, pattern, size): + """Render one texture; returns {suffix: path} for _BC and _R.""" + base_fn, rough_fn = pattern + paths = {} + for suffix, fn in (("BC", base_fn), ("R", rough_fn)): + # Wrap in a function so the inner loop stays tight. + rows = [] + for y in range(size): + v = y / size + row = bytearray() + for x in range(size): + u = x / size + value = fn(u, v, x / size * size / 64.0, y / size * size / 64.0) + if isinstance(value, tuple): + row += bytes((clamp8(value[0]), clamp8(value[1]), clamp8(value[2]))) + else: + grey = clamp8(value) + row += bytes((grey, grey, grey)) + rows.append(bytes(row)) + path = os.path.join(OUT_DIR, f"{name}_{suffix}.png") + write_png(path, size, size, rows) + paths[suffix] = path + return paths + + +def main(argv=None) -> int: + global OUT_DIR + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--out", default="/tmp/gigline/textures", + help="output directory (default /tmp/gigline/textures)") + ap.add_argument("--size", type=int, default=512, help="edge length in pixels") + args = ap.parse_args(argv) + OUT_DIR = args.out + + print(f"writing {len(TEXTURES) * 2} maps at {args.size}² to {OUT_DIR}") + for name, pattern in TEXTURES.items(): + paths = render(name, pattern, args.size) + for suffix, path in paths.items(): + print(f" {os.path.basename(path):28} {os.path.getsize(path):>8} bytes") + return 0 + + +OUT_DIR = "/tmp/gigline/textures" + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 4dcd7bb..33e5f2b 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -16,10 +16,13 @@ # deterministically instead of stacking duplicates. import os +import sys import unreal MAP = "/Game/Hollywood/DemoMap" +# Repository root, so the texture generator can be invoked by path. +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) RESULT = "/tmp/gigline/demo-map.txt" lines = [] @@ -50,7 +53,46 @@ def spawn(cls, loc, label, rot=None): return actor -def ensure_material(mat_name, spec): +TEXTURE_DIR = "/Game/Hollywood/Textures" + + +def import_texture(png_path, asset_name): + """Import a generated PNG as a Texture2D (idempotent: replaces the asset).""" + if not os.path.exists(png_path): + log(f"texture missing: {png_path}") + return None + task = unreal.AssetImportTask() + task.filename = png_path + task.destination_path = TEXTURE_DIR + task.destination_name = asset_name + task.automated = True + task.save = True + task.replace_existing = True + ui = unreal.TextureImportUI() if hasattr(unreal, "TextureImportUI") else None + if ui: + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + return unreal.EditorAssetLibrary.load_asset(f"{TEXTURE_DIR}/{asset_name}") + + +def ensure_textures(): + """Generate/point at the facade maps and import them. Returns {name: asset}.""" + out_dir = "/tmp/gigline/textures" + gen = os.path.join(ROOT, "Tools", "generate_facade_textures.py") + if os.path.exists(gen) and not os.path.exists(os.path.join(out_dir, "T_StoneAshlar_BC.png")): + import subprocess + subprocess.run([sys.executable, gen, "--out", out_dir, "--size", "512"], check=False) + imported = {} + for name in ("T_StoneAshlar_BC", "T_StoneAshlar_R", "T_Stucco_BC", "T_Stucco_R", + "T_Asphalt_BC", "T_Asphalt_R", "T_Concrete_BC", "T_Concrete_R"): + asset = import_texture(os.path.join(out_dir, name + ".png"), name) + if asset: + imported[name] = asset + log(f"textures imported: {len(imported)}/8") + return imported + + +def ensure_material(mat_name, spec, textures=None): """Create/reuse a look-developed material under /Game/Hollywood/Site. Blender's flat diffuse colours do not survive the FBX -> Unreal import (the @@ -165,6 +207,44 @@ def ensure_material(mat_name, spec): link(joint, tinted, "Alpha") base_color_expr = tinted + # If a texture set is supplied for this material, sample it world-aligned and + # use it instead of the graph-only coursing: the map carries the coursing and + # the grain, so overlaying both would double the pattern. + tex = (textures or {}).get(spec.get("texture", "")) + if tex: + bc = (textures or {}).get(spec.get("texture", "") + "_BC") + # world position (cm) -> UV: one texture tile every tile_cm centimetres + tile_cm = float(spec.get("tile_cm", 300.0)) + uv_scale = node(unreal.MaterialExpressionMultiply) + link(wp, uv_scale, "A") + link(scalar(1.0 / tile_cm), uv_scale, "B") + mask_xy = node(unreal.MaterialExpressionComponentMask) + for pin in ("R", "G"): + try: + mask_xy.set_editor_property(pin, True) + except Exception: # noqa: BLE001 + pass + try: + mask_xy.set_editor_property("B", False) + mask_xy.set_editor_property("A", False) + except Exception: # noqa: BLE001 + pass + link(uv_scale, mask_xy) + if bc: + sample = node(unreal.MaterialExpressionTextureSample) + sample.set_editor_property("texture", bc) + link(mask_xy, sample, "UVs") + lib.connect_material_property(sample, "RGB", + unreal.MaterialProperty.MP_BASE_COLOR) + base_color_expr = sample + r_map = (textures or {}).get(spec.get("texture", "") + "_R") + if r_map: + r_sample = node(unreal.MaterialExpressionTextureSample) + r_sample.set_editor_property("texture", r_map) + link(mask_xy, r_sample, "UVs") + lib.connect_material_property(r_sample, "R", + unreal.MaterialProperty.MP_ROUGHNESS) + lib.connect_material_property(base_color_expr, "", unreal.MaterialProperty.MP_BASE_COLOR) # Roughness: lerp(base, base + variation, noise) so the highlight breaks up. @@ -337,9 +417,11 @@ def build(): # Look-developed surface materials (see ensure_material for the graph). MATERIAL_SPECS = { "M_Asphalt": dict(dark=(0.040, 0.040, 0.044), bright=(0.072, 0.072, 0.078), - roughness=0.92, specular=0.22, noise_scale=0.006), + roughness=0.92, specular=0.22, noise_scale=0.006, + texture="T_Asphalt", tile_cm=900.0), "M_Concrete": dict(dark=(0.44, 0.42, 0.39), bright=(0.56, 0.54, 0.50), - roughness=0.88, specular=0.28, noise_scale=0.004), + roughness=0.88, specular=0.28, noise_scale=0.004, + texture="T_Concrete", tile_cm=250.0), "M_KerbConcrete": dict(dark=(0.47, 0.45, 0.42), bright=(0.60, 0.58, 0.54), roughness=0.85, specular=0.30, noise_scale=0.008), "M_Ground": dict(dark=(0.22, 0.20, 0.17), bright=(0.33, 0.30, 0.26), @@ -352,12 +434,13 @@ def build(): "M_FacadeStone": dict(dark=(0.62, 0.58, 0.50), bright=(0.76, 0.72, 0.64), roughness=0.70, specular=0.32, noise_scale=0.005, courses_m=0.55, joint_width=0.11, - joint_tint=(0.40, 0.37, 0.32), roughness_variation=0.12), + joint_tint=(0.40, 0.37, 0.32), roughness_variation=0.12, + texture="T_StoneAshlar", tile_cm=440.0), # Rendered/stucco frontage: no coursing, but a fine tooth and a wider # roughness range than before, because flat stucco is what read as plastic. "M_Stucco": dict(dark=(0.50, 0.46, 0.40), bright=(0.68, 0.64, 0.57), roughness=0.76, specular=0.25, noise_scale=0.0025, - roughness_variation=0.20), + roughness_variation=0.20, texture="T_Stucco", tile_cm=260.0), # Stone in shadow: the hero's recessed wall behind the colonnade. Same # coursing as the face, a stop darker, so depth reads without a light. "M_FacadeStoneShadow": dict(dark=(0.40, 0.37, 0.32), bright=(0.52, 0.49, 0.43), @@ -385,9 +468,10 @@ def build(): "Kerb_North": "M_KerbConcrete", "Crossing_Markings": "M_RoadPaint", } + textures = ensure_textures() built = {} for mat_name, spec in MATERIAL_SPECS.items(): - built[mat_name] = ensure_material(mat_name, spec) + built[mat_name] = ensure_material(mat_name, spec, textures) imported = 0 for name, mat_name in SURFACE_MATERIALS.items(): -- 2.47.3 From c9d603f754b06a7da4b79122d2f0a75107b3f3e0 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:12:45 +0100 Subject: [PATCH 04/13] =?UTF-8?q?feat(kit):=20fa=C3=A7ade=20module=20kit?= =?UTF-8?q?=20at=20the=204.96=20m=20bay,=20assembled=20onto=20eight=20buil?= =?UTF-8?q?dings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tools/facade_kit_build.py: the reusable kit (pilaster, sill, lintel, window bay, shopfront bay, door bay, cornice, parapet + coping) at the measured 4.96 m bay and 3.10 m storey; 16 meshes exported per object through the shared preset with a JSON part report recording dims and evidence. - Tools/ue_import_facade_kit.py: imports to /Game/Hollywood/Kit with complex-as-simple collision, so thin panes keep their real faces. - Tools/ue_apply_facade_kit.py: assembles from each building's own bounding box (bays = round(width/4.96), storeys above the ground floor) with shopfronts at street level, windows above, pilasters on bay boundaries, cornice and parapet at the top. Clears a previous application; unique labels per building. 638 pieces across six frontage buildings and both north buildings. Two bugs fixed en route: the window piece was modelled entirely at +Y so it sat inside the solid wall and was invisible (now projects 3 cm proud with glazing 6 cm inset), and clearing materials mid-build invalidated handles. Screenshots re-rendered; window rhythm now reads (edge transitions per row rose from flat to ~197 on the spawn view). --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Kit/Kit_Coping.uasset | 3 + .../Content/Hollywood/Kit/Kit_Cornice.uasset | 3 + .../Content/Hollywood/Kit/Kit_Door.uasset | 3 + .../Hollywood/Kit/Kit_DoorJamb_L.uasset | 3 + .../Hollywood/Kit/Kit_DoorJamb_R.uasset | 3 + .../Content/Hollywood/Kit/Kit_Lintel.uasset | 3 + .../Content/Hollywood/Kit/Kit_Parapet.uasset | 3 + .../Content/Hollywood/Kit/Kit_Pilaster.uasset | 3 + .../Hollywood/Kit/Kit_ShopFascia.uasset | 3 + .../Hollywood/Kit/Kit_ShopGlass.uasset | 3 + .../Hollywood/Kit/Kit_ShopPier_L.uasset | 3 + .../Hollywood/Kit/Kit_ShopPier_R.uasset | 3 + .../Content/Hollywood/Kit/Kit_Sill.uasset | 3 + .../Hollywood/Kit/Kit_Threshold.uasset | 3 + .../Hollywood/Kit/Kit_WindowBay.uasset | 3 + .../Hollywood/Kit/Kit_WindowGlass.uasset | 3 + .../Hollywood/Kit/M_Kit_cornice.uasset | 3 + .../Content/Hollywood/Kit/M_Kit_door.uasset | 3 + .../Content/Hollywood/Kit/M_Kit_lintel.uasset | 3 + .../Hollywood/Kit/M_Kit_parapet.uasset | 3 + .../Hollywood/Kit/M_Kit_pilaster.uasset | 3 + .../Hollywood/Kit/M_Kit_shopfront.uasset | 3 + .../Content/Hollywood/Kit/M_Kit_sill.uasset | 3 + .../Content/Hollywood/Kit/M_Kit_window.uasset | 3 + Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 23 ++ Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- Screenshots/review-character.png | 4 +- Screenshots/review-ground-contact.png | 4 +- Screenshots/review-hero-facade-front.png | 4 +- Screenshots/review-hero-facade-oblique.png | 4 +- Screenshots/review-hero-portals.png | 4 +- Screenshots/review-npc-prefab.png | 4 +- Screenshots/review-overview.png | 4 +- Screenshots/review-player-placement.png | 4 +- Screenshots/review-scale-cubes.png | 4 +- Screenshots/review-shadow-compare.png | 4 +- TODO.md | 2 +- Tools/README.md | 3 + Tools/facade_kit_build.py | 285 ++++++++++++++++++ Tools/ue_apply_facade_kit.py | 210 +++++++++++++ Tools/ue_import_facade_kit.py | 96 ++++++ 46 files changed, 721 insertions(+), 32 deletions(-) create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset create mode 100644 Tools/facade_kit_build.py create mode 100644 Tools/ue_apply_facade_kit.py create mode 100644 Tools/ue_import_facade_kit.py diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 34b7b1e..53c90ff 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:0dc9468e94f868cd76893dd1d8e6cd2ef0b365ce3fa86f3feed5f65a630af9cb -size 55066 +oid sha256:cabcd908ff5149ac64773f34092efbdf7df077165db50438aef1d36e846ecc46 +size 939345 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset new file mode 100644 index 0000000..47b6cdb --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8c34895de32da94b4d773b3b7be342c4468295401d4701a3bdc93ed923e9f10 +size 72484 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset new file mode 100644 index 0000000..97fd0f2 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01b734f5cf0d08395e924b2c2ba5c6b8fdb08041a474ed24f4fb151323b32dd9 +size 72493 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset new file mode 100644 index 0000000..b833aa7 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e50c198203e70bc926f17c056978c5c7a93d1705c30600c397e520c4f4c4f42 +size 72369 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset new file mode 100644 index 0000000..dfb83cc --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d554e6f5122f5b7ed9888432e5941522cab5c9a28a2c65b0bf5ba6f2b9c24ec +size 72483 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset new file mode 100644 index 0000000..089c170 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4ac06e8d69b569adda3b120944fa41ab8e4ca1a544fa4263d61b9439552319f +size 72492 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset new file mode 100644 index 0000000..58fc7e7 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6437b4b48e40b650f9c13db22bd34979361b2da2521e55aedc3000c0ae416fad +size 72449 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset new file mode 100644 index 0000000..ef8a281 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96f42acaadd77b069bc6da62c646183ded8c0c8b2f239121a3d737b86a2b113f +size 72482 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset new file mode 100644 index 0000000..bea3a6d --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd3a3956c2384b15e38a67fb2b0f350bbf9d073bf204efe282bc84c2605e5331 +size 72535 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset new file mode 100644 index 0000000..797f05b --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fd1b3f2439e60cb7d3578215543cecd60c7aeb2115099268f44dad470bf91d0 +size 72617 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset new file mode 100644 index 0000000..de30ffd --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:680337f0ed0e2b8c58495e539d87766d76f688b979908e39f12e1372d137872b +size 72600 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset new file mode 100644 index 0000000..3515bae --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e86da75085c4abf9d8b4eea5a9b77f208f0bb811d3418fb33ca5da2febf436de +size 72612 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset new file mode 100644 index 0000000..5d13aea --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35a391ad6bd9dbe9be1ac5ec24848d94d91de3831810748b8b7d4ea2dd1dd663 +size 72620 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset new file mode 100644 index 0000000..2ea504e --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1dd748d23ab0caaf1b13f3a85b4a2d11a89e2f6cff311b8326750f517a2e59d +size 72365 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset new file mode 100644 index 0000000..546a234 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a54520339bb3453b866e7da7fde9bd5675a3af2b641766df47604e328f46ae8 +size 72465 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset new file mode 100644 index 0000000..794f4d5 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:601098d4b531b20be1243c06644cc7ddf70e4e4f3d746c6b5efba080fdf593ac +size 72530 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset new file mode 100644 index 0000000..e82ebd2 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:304d251f899f05b113f8011b24dcd9090e9c4f83a9d8658db55a3399ba98ddd3 +size 72565 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset new file mode 100644 index 0000000..925853a --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb17b057f18958dd2086800f37fe1c951166aaeb0c7f2b842f2ab5cff786849d +size 56606 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset new file mode 100644 index 0000000..f140901 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d92a56b405a8eba2db4e31c5603b77195ba297f0dce70e4ab750a63b84ab1155 +size 56549 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset new file mode 100644 index 0000000..d80faf0 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b235d479ea427b0bf4e495f4e74bad0853bdc466d8eaf9a116f49f53905a75e +size 56587 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset new file mode 100644 index 0000000..2bd7207 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:550e00ba4d59e75e0f5524dc278b34c8c0bd2dae1ab9f9567bed2b3fd820f7c1 +size 56595 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset new file mode 100644 index 0000000..4a29ddf --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae26b34392a8f173a82d5936449a93a882b7cec52eb788793447352e59fe8443 +size 56625 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset new file mode 100644 index 0000000..0e6215f --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1610ba046a1b76230aad2f6990bcd506eb3ee479248a442f21fee32ee9337612 +size 56647 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset new file mode 100644 index 0000000..6ce3d70 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:821549d7ed69a2c726d7e6fa2dafafa72fb45245a5c0792f532cac52be79c5ef +size 56560 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset new file mode 100644 index 0000000..7066f64 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d034b7c099db62f88d9a6df23e6aa7843cf4b90689e90e4836011b9b1e1f88a9 +size 56607 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index bd50cc5..4341632 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06db40a8beab60daaf548554fc70b85eb4cd4ab1f8371b0e8ccd4b7e33c1648d +oid sha256:d0bb3ac4675c0622116d74233da208c1ba97e686e0377e99ef8a77dc49bcbb24 size 28811 diff --git a/MEMORY.md b/MEMORY.md index 5834e6d..17e55eb 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -568,3 +568,26 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - The script needs `ROOT` in `ue_build_demo_map.py` and `sys` imported; forgetting either fails with `NameError` and an exit code of 0 from the commandlet (always read the result file under /tmp/gigline). + +## 2026-11-09 — façade module kit (0.1.4) + +- `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay** and + **3.10 m storey**: pilaster, sill, lintel, window bay, shopfront bay, door bay, + cornice, parapet + coping = 16 meshes, exported per-object with + `fbx_export_preset` plus a JSON part report (dims + evidence per piece). +- `Tools/ue_import_facade_kit.py` → `/Game/Hollywood/Kit/*`, complex-as-simple + collision (a convex hull would fence off the shopfront glazing). +- `Tools/ue_apply_facade_kit.py` lays the kit out from each building's own bbox: + `bays = round(width / 496 cm)`, storeys from height minus the 310 cm ground + floor. **638 pieces** over six frontage buildings + both north buildings. +- **Bug worth remembering:** the first window piece was modelled entirely at +Y + (into the wall), so every window sat *inside* the solid blockout wall and was + invisible in every render while the placement maths looked right. The kit now + projects the surround 3 cm proud with the glazing 6 cm behind its face — a + solid-wall blockout cannot carry a real recess, so the piece has to project. +- Blender gotcha: clearing the scene *including materials* invalidates handles + already taken ("StructRNA of type Material has been removed"); clear objects and + orphan meshes only, and create the material after the clear. +- The kit meshes are spawned at the world origin with their position baked into + the mesh (like the frontage exports), so placement uses the bbox directly and + must not add the actor location. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index ce2ce2d..76af3a1 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b1aeaed19783379067f81e67c4286bb28017c50c4fa713574ac18370c3846bb -size 2755870 +oid sha256:e38ac20db95e026e2a3031dc4a7044eade0052f21790ce24479e72be0668a6a3 +size 2754407 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 1c4503e..3748bc7 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d83115402d0a44b8b025dc8ba35505bba8fc1410ead6f830df8c819d3020e79 -size 2267060 +oid sha256:632f36c7708e60afa8249341ba8e9c92d27dd486d412af50d91f32394db8d523 +size 2262962 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 0eaeefd..fdec6f2 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:701203ac6afe5d7897e32414bce1bc09dc85cfd747636104ab5b3cad48306d47 -size 2838039 +oid sha256:fbc59fa80c5d6769f88bf120246cd1d18d885264cbccb91bd9e98fa6e1d31f87 +size 2773580 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index fc88e69..770b478 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2607aaa8e9333dd033745ebbac6b99b31641049e03982d66d5d1a0b1b086de9d -size 2703487 +oid sha256:4a612caffb70624590152784204d17c0816c6e9dd588b2ea9da60f95cc463830 +size 2676882 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index de5a517..838d2c8 100644 --- a/Screenshots/review-character.png +++ b/Screenshots/review-character.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d38dc63e0c09483ec15aaa493b483baa8d6d7d7a2678415fef5519561226ebd -size 2540302 +oid sha256:772930bca1b302578fd771a7d8f5be7da82b5c6566f9efe7344561a148de1635 +size 2536185 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index 113c7d5..6cb7bd5 100644 --- a/Screenshots/review-ground-contact.png +++ b/Screenshots/review-ground-contact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2797ca6c909c3956795361fcef86dfa8268007ab3df23adf07be807d0f0e073f -size 1750724 +oid sha256:54c81c33f84515be3c947a3b7d1376eefe9e8d017165487441128d95c258add6 +size 1754197 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index d290e35..e03c6e8 100644 --- a/Screenshots/review-hero-facade-front.png +++ b/Screenshots/review-hero-facade-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f428e43ac9117e9e45a662a89d82f5b636bd8eebd07cad5de8c8c3894a146b09 -size 2298703 +oid sha256:88d440c07d0220e880af430249f1d2eafa3fe780ab7d5367ca1b299761ea9400 +size 2294756 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index d603bef..90665d9 100644 --- a/Screenshots/review-hero-facade-oblique.png +++ b/Screenshots/review-hero-facade-oblique.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae882d2fb120ce45c4bed16c4e5288400bbbe9e565a93f29768e353ebb280870 -size 1515587 +oid sha256:dfd3ad70d01a466316a64f34cde6cf7f699b6e9cb7bdaee6e26873c5c912b557 +size 1510048 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index b73d080..9569846 100644 --- a/Screenshots/review-hero-portals.png +++ b/Screenshots/review-hero-portals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:795bffa54e8efc0bb3aafb47a3cfa6282ea41c3a55a0402fb349c2e7ddbedd53 -size 1321845 +oid sha256:05c63be2a22a2a35bd95c7c7467da67f9556d0ea56395149e860476cdf9f34bd +size 1322804 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index f0c8c75..4bb5b3e 100644 --- a/Screenshots/review-npc-prefab.png +++ b/Screenshots/review-npc-prefab.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12ba9d44bb231629abf2e0a607d22e1c7c940b953b56c089efb8810d8ffce88a -size 1736896 +oid sha256:732b96208e25edc36f6e52cc0b214540d07fc6484d0d1cc76df438ce99b050fc +size 1735007 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index 0a51b2a..e0452d1 100644 --- a/Screenshots/review-overview.png +++ b/Screenshots/review-overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d3ccc49c65e6347e273d07fe7cf72839ad3b4b74c37ab9e4e1d92f4020b4390 -size 2614061 +oid sha256:67bb53592ca9e3bfaae5b5feb4d772fdbc474ed3bedd78b81cb999a44aa4a839 +size 2488342 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index c80f9f1..00dc1af 100644 --- a/Screenshots/review-player-placement.png +++ b/Screenshots/review-player-placement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8ad82386c9504b3e2bdd461c22c57e2e9cb19762aaf794f47dd8472ff01f436 -size 2025973 +oid sha256:3c01fcc7ee234597c8451154ebe9b91bf5aa0720d963b1ee5694835aa5133ad2 +size 2030579 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 71f4bef..9e1bc57 100644 --- a/Screenshots/review-scale-cubes.png +++ b/Screenshots/review-scale-cubes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3aa35281f616ad86f1d7b2de6a79b0bf3fd476b8c259dc3d3449c6ef7f3dc960 -size 1942628 +oid sha256:945099a7cf7a5f185ab445fdb0c6906353fa3789ec7eb4f8d573632b3c965d18 +size 1935381 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index aa480e0..161d09f 100644 --- a/Screenshots/review-shadow-compare.png +++ b/Screenshots/review-shadow-compare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7a03d6be117470d86bd351aa35584350140bcf6ff2aae3b35da79e062672a4c -size 1723008 +oid sha256:99b64891c9b211d9990bfbf39b10f3df73ca3be324461550184d9ee4dec5db17 +size 1711185 diff --git a/TODO.md b/TODO.md index 4388110..e22ff48 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,7 @@ Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the faç ### Patch tasks - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `facades-0.1.4`) - [ ] **Finish the Blosm blockout cleaning/detailing (moved from the deferred `0.1.0` item).** Import state: `Art/blender/blockout/blockout-2026-11-06.blend` (86 meshes, EPSG:26911, 0.0 cm affine residual) with cleaning tooling in `Geo/tools/` and `Art/blender/blockout/blosm_blockout.py`. To do here: (a) untextured roofs/facades → **Replace placeholder materials** below; (b) Blosm `setupScript` façade styles → **Give OSM buildings a façade style** below; (c) the 3 of 15 buildings lacking height data are the `0.1.2` placeholder-heights item. -- [ ] **Define the façade module.** One set of reusable pieces (bay, pilaster, cornice, parapet, window, shopfront, door) at a fixed module, exported with the `ue_metric_v1` preset so every building assembles from the same kit. +- [x] **Define the façade module.** **Done 2026-11-09.** `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay**: pilaster, sill, lintel, window bay (surround + inset glazing), shopfront bay (piers, glass, fascia), door bay (jambs, door, threshold), cornice and parapet + coping — 16 meshes, each exported through the shared `ue_metric_v1` preset with a JSON part report recording dims and evidence. `Tools/ue_import_facade_kit.py` imports to `/Game/Hollywood/Kit`. `Tools/ue_apply_facade_kit.py` assembles it from each building's own bounding box: **638 pieces placed across the six frontage buildings and both north buildings** (6922 = 13 bays × 11 storeys, theatre 8 × 5, TCL and Tussaud's 9 × 4, etc.). First pass put the window bays inside the solid wall (invisible) — the kit now projects the surround 3 cm proud with glazing 6 cm behind it. - [A] **Replace placeholder materials.** **In progress 2026-11-09.** `ensure_material()` now builds masonry coursing (world-space course height + joint band + tint, corrected for UE's centimetre WorldPosition) and roughness variation; new `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5 without a refraction pass) and `M_FacadeBanner`; assignment is per **slot** keyed on the imported slot name (it was slot-0 only, which on the hero is the banner, so stone/glass/shadow were still flat). Hero: 4/4 slots, frontage and north buildings 2/2. Remaining: bake real texture maps (brick/stone/asphalt generators) rather than graph-only detail, and give the frontage shopfront line its own material. - [ ] **Give OSM buildings a façade style.** Blosm exposes `scene.blosm.setupScript` (`setup/neoclassical.py` is the example) so a building can carry a real elevation instead of a plain extrusion. Build one style script for the south frontage and one for the north. - [ ] **Hero façade detail pass.** Ionic capitals and fluting, the parapet motto band and medallions as geometry or decal, window rhythm behind the colonnade, and the three portal flights at their photographed proportions. diff --git a/Tools/README.md b/Tools/README.md index f54bb8d..97c90cb 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -16,6 +16,9 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | Tool | Purpose | | --- | --- | | `check-lfs.sh` | Fails if the `lfs` filter is missing or an LFS-tracked file was committed as a plain blob | +| `facade_kit_build.py` | Builds the reusable façade **module kit** at the measured 4.96 m bay: pilaster, sill, lintel, window bay (surround + glazing), shopfront bay (piers, glass, fascia), door bay (jambs, door, threshold), cornice and parapet + coping. Exports each piece with the shared preset plus a JSON part report (dims + evidence per piece). | +| `ue_import_facade_kit.py` | Imports the kit into `/Game/Hollywood/Kit/*` as static meshes with complex-as-simple collision; writes `/tmp/gigline/kit-import.txt`. | +| `ue_apply_facade_kit.py` | Lays the kit over a building's street face from its own bounding box: bay count = round(width / 4.96), storeys from the height, shopfronts on the ground floor, windows above, pilasters on bay boundaries, cornice and parapet at the top. Clears a previous application first; unique labels per building. | | `generate_facade_textures.py` | Deterministic stdlib PNG generator for the facade maps (`T_StoneAshlar`, `T_Stucco`, `T_Asphalt`, `T_Concrete`, each `_BC` base colour + `_R` roughness). Pure-zlib writer, hash-based value noise, identical bytes on every run; `--size` and `--out` (`/tmp/gigline/textures` by default). `ue_build_demo_map.py` imports them into `/Game/Hollywood/Textures` and samples them world-aligned. | | `ue_common.py` | Shared helpers for the Unreal commandlet scripts: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False silently), and `require_saved()` makes that failure explicit. | | `fbx_export_preset.py` | The **single** FBX export preset (`ue_metric_v1` / `ue_character_v1`): metres→centimetres via `FBX_SCALE_NONE`, axis mapping east +X / north −Y / up +Z, `mesh_smooth_type="FACE"` (smoothing groups survive the import). Every Blender exporter imports it instead of copying the argument block; `preset_kwargs()` is bpy-free and unit-tested. | diff --git a/Tools/facade_kit_build.py b/Tools/facade_kit_build.py new file mode 100644 index 0000000..c60e778 --- /dev/null +++ b/Tools/facade_kit_build.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +"""Build the reusable façade module kit (0.1.4). + +One module, measured: the **4.96 m bay** (the hero's column rhythm, which the +El Capitan Theatre next door shares per the reference notes). Every block building +should assemble from the same pieces instead of being a bare extrusion, so the +street wall gets a consistent window rhythm, shopfront line, cornice and parapet. + +Pieces (`ue_metric_v1`, metres, origin at the module's bottom-front centre so a +building can place them by bay index and storey index): + + Kit_Pilaster vertical pier on a bay boundary + Kit_Sill window sill band + Kit_Lintel window head / lintel band + Kit_WindowBay recessed window opening in one bay (glass + reveal) + Kit_ShopfrontBay ground-floor shopfront: piers, glass, fascia + Kit_DoorBay entrance bay: recessed door, jambs, threshold + Kit_Cornice projecting cornice band, one module long + Kit_Parapet parapet wall + coping, one module long + +Dimensions come from the measured storey height and the reference photography +where it resolves them (window proportions, shopfront line); everything the +photographs do not resolve is an assumption and is recorded as one in the part +report, so model and evidence stay separable. + + python3 Tools/facade_kit_build.py --out /tmp/gigline/kit \ + --report /tmp/gigline/kit-report.json + blender -b --factory-startup --python Tools/facade_kit_build.py -- --out /tmp/gigline/kit +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import sys + +import bpy + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import fbx_export_preset as fbx_preset # noqa: E402 + +# --- module ------------------------------------------------------------------ + +BAY = 4.96 # m, measured: hero column rhythm, shared by the theatre +STOREY = 3.10 # m, the calibrated level height (see block_height_report) +PARAPET_H = 0.90 # m, coping above the top storey +CORNICE_H = 0.55 # m, projecting band +CORNICE_OUT = 0.35 # m, how far the cornice projects + +PILASTER_W = 0.60 +PILASTER_D = 0.28 + +WINDOW_W = 1.60 +WINDOW_H = 1.85 +WINDOW_REVEAL = 0.18 # how far the glass sits behind the wall face +SILL_H = 0.14 +LINTEL_H = 0.34 + +SHOP_GLASS_H = 2.30 # shopfront glazing height from the pavement +SHOP_FASCIA_H = 0.55 # sign band above the glazing +DOOR_W = 1.90 +DOOR_H = 2.45 + +STONE = None # materials are assigned in Unreal; Blender carries one grey +PARTS: list[dict] = [] + + +def make_material(name, value): + mat = bpy.data.materials.new(name) + mat.use_nodes = True + bsdf = mat.node_tree.nodes.get("Principled BSDF") + bsdf.inputs["Base Color"].default_value = (value, value * 0.98, value * 0.94, 1.0) + bsdf.inputs["Roughness"].default_value = 0.75 + return mat + + +def note(obj, note_text): + PARTS.append({ + "object": obj.name, + "dims_m": [round(d, 3) for d in obj.dimensions], + "location_m": [round(c, 3) for c in obj.location], + "evidence": note_text, + }) + print(f"[kit] {obj.name:22} {obj.dimensions.x:.2f} x {obj.dimensions.y:.2f} " + f"x {obj.dimensions.z:.2f} m ({note_text})") + + +def cube(name, x, y, z, dx, dy, dz, material): + bpy.ops.mesh.primitive_cube_add(size=1.0, location=(x, y, z)) + obj = bpy.context.active_object + obj.name = name + obj.scale = (dx, dy, dz) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + obj.data.materials.append(material) + return obj + + +def cylinder(name, x, y, z0, z1, radius, material, vertices=16): + bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, + depth=(z1 - z0), location=(x, y, (z0 + z1) / 2.0)) + obj = bpy.context.active_object + obj.name = name + obj.data.materials.append(material) + bpy.ops.object.shade_smooth_by_angle(angle=math.radians(35.0)) + return obj + + +def clear_scene(): + """Remove objects and orphan meshes. Materials are left alone: deleting them + invalidates handles the previous piece still holds ("StructRNA of type + Material has been removed").""" + bpy.ops.object.select_all(action="SELECT") + bpy.ops.object.delete(use_global=False) + for mesh in list(bpy.data.meshes): + if mesh.users == 0: + bpy.data.meshes.remove(mesh) + + +# --- pieces ------------------------------------------------------------------ +# Each builder returns [(object, note)] for one module. Origin: bottom-front +# centre of the module, +Y into the wall depth, +Z up, X across the bay. + + +def build_pilaster(mat): + o = cube("Kit_Pilaster", 0.0, PILASTER_D / 2.0, STOREY / 2.0, + PILASTER_W, PILASTER_D, STOREY, mat) + return [(o, "assumption: 0.60 x 0.28 m pier on the bay boundary")] + + +def build_sill(mat): + o = cube("Kit_Sill", 0.0, 0.10, SILL_H / 2.0, WINDOW_W + 0.30, 0.24, SILL_H, mat) + return [(o, "assumption: projecting stone sill under each opening")] + + +def build_lintel(mat): + o = cube("Kit_Lintel", 0.0, 0.10, LINTEL_H / 2.0, WINDOW_W + 0.30, 0.24, LINTEL_H, mat) + return [(o, "assumption: lintel band over each opening")] + + +def build_window_bay(mat): + """One storey of window: a surround standing proud of the wall, glazing inset. + + The blockout wall is a solid box, so a real recess cannot be cut. The piece is + therefore a shallow surround that projects 3 cm and a glazing plane set 6 cm + behind its front, which reads as a window from any street angle. The first + version modelled everything at +Y (into the wall), so the whole bay sat inside + the solid wall and was invisible in every render. + """ + parts = [] + surround_d = 0.22 + surround = cube("Kit_WindowBay", 0.0, -0.03 + surround_d / 2.0, WINDOW_H / 2.0, + WINDOW_W, surround_d, WINDOW_H, mat) + parts.append((surround, "photographs: window proportions 1.60 x 1.85 m; surround " + "projects 3 cm so it reads against a solid blockout wall")) + glass = cube("Kit_WindowGlass", 0.0, 0.09, WINDOW_H / 2.0, + WINDOW_W - 0.16, 0.04, WINDOW_H - 0.16, mat) + parts.append((glass, "glazing plane set 6 cm behind the surround face; " + "material assigned in Unreal")) + return parts + + +def build_shopfront_bay(mat): + """Ground floor: two piers, glazing between, fascia above.""" + parts = [] + for side in (-1, 1): + x = side * (BAY / 2.0 - PILASTER_W / 2.0) + o = cube(f"Kit_ShopPier_{'L' if side < 0 else 'R'}", x, PILASTER_D / 2.0, + SHOP_GLASS_H / 2.0, PILASTER_W, PILASTER_D, SHOP_GLASS_H, mat) + parts.append((o, "shopfront pier, same 0.60 m module as the pilaster")) + glass = cube("Kit_ShopGlass", 0.0, 0.06, SHOP_GLASS_H / 2.0, + BAY - 2 * PILASTER_W, 0.05, SHOP_GLASS_H - 0.25, mat) + parts.append((glass, "photographs: continuous shopfront glazing to a " + "2.30 m head")) + fascia = cube("Kit_ShopFascia", 0.0, 0.14, SHOP_GLASS_H + SHOP_FASCIA_H / 2.0, + BAY, 0.26, SHOP_FASCIA_H, mat) + parts.append((fascia, "sign band over the shopfront; 0.55 m assumption")) + return parts + + +def build_door_bay(mat): + parts = [] + for side in (-1, 1): + x = side * (DOOR_W / 2.0 + 0.35) + o = cube(f"Kit_DoorJamb_{'L' if side < 0 else 'R'}", x, 0.14, STOREY / 2.0, + 0.35, 0.30, STOREY, mat) + parts.append((o, "entrance jamb standing 0.30 m proud of the wall")) + door = cube("Kit_Door", 0.0, 0.06, DOOR_H / 2.0, DOOR_W, 0.09, DOOR_H, mat) + parts.append((door, "assumption: 1.90 x 2.45 m recessed door")) + threshold = cube("Kit_Threshold", 0.0, 0.22, 0.05, DOOR_W + 0.70, 0.44, 0.10, mat) + parts.append((threshold, "threshold slab, flush with the pavement")) + return parts + + +def build_cornice(mat): + o = cube("Kit_Cornice", 0.0, -CORNICE_OUT / 2.0, CORNICE_H / 2.0, + BAY, CORNICE_OUT * 2.0, CORNICE_H, mat) + return [(o, "projecting cornice band, one bay long; 0.35 m projection assumption")] + + +def build_parapet(mat): + parts = [] + wall = cube("Kit_Parapet", 0.0, -0.15, PARAPET_H / 2.0, BAY, 0.55, PARAPET_H, mat) + parts.append((wall, "assumption: 0.90 m parapet above the top storey")) + coping = cube("Kit_Coping", 0.0, -0.15, PARAPET_H - 0.06, BAY, 0.65, 0.12, mat) + parts.append((coping, "coping slab over the parapet")) + return parts + + +BUILDERS = { + "pilaster": build_pilaster, + "sill": build_sill, + "lintel": build_lintel, + "window": build_window_bay, + "shopfront": build_shopfront_bay, + "door": build_door_bay, + "cornice": build_cornice, + "parapet": build_parapet, +} + + +def build_piece(name): + """Build one piece group in a clean scene. The material is created *after* + the clear so its handle stays valid.""" + clear_scene() + mat = make_material(f"M_Kit_{name}", 0.6) + return BUILDERS[name](mat) + + +def export_group(objects, out_dir): + """Export each object to its own FBX with the shared preset.""" + exported = [] + for obj in objects: + bpy.ops.object.select_all(action="DESELECT") + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + path = os.path.join(out_dir, obj.name + ".fbx") + fbx_preset.export_static(path, selection=True) + exported.append(path) + return exported + + +def main(argv=None) -> int: + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--out", default="/tmp/gigline/kit") + ap.add_argument("--report", default=None) + ap.add_argument("--only", default=None, help="comma-separated piece names") + args = ap.parse_args(argv) + os.makedirs(args.out, exist_ok=True) + + names = [n.strip() for n in args.only.split(",")] if args.only else list(BUILDERS) + unknown = [n for n in names if n not in BUILDERS] + if unknown: + raise SystemExit(f"unknown piece(s): {unknown}; known: {sorted(BUILDERS)}") + + total = 0 + for name in names: + made = build_piece(name) + if name == "window": + made = made # window bay exports two objects (reveal + glass) + objects = [o for o, _ in made] + for obj, note_text in made: + note(obj, note_text) + exported = export_group(objects, args.out) + total += len(exported) + print(f"[kit] {name}: {len(exported)} object(s) exported") + + if args.report: + with open(args.report, "w", encoding="utf-8") as handle: + json.dump({ + "module_m": BAY, + "storey_m": STOREY, + "pieces": sorted(BUILDERS), + "parts": PARTS, + }, handle, indent=2) + print(f"[kit] report -> {args.report}") + print(f"[kit] exported {total} object(s) to {args.out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/ue_apply_facade_kit.py b/Tools/ue_apply_facade_kit.py new file mode 100644 index 0000000..b33f1e3 --- /dev/null +++ b/Tools/ue_apply_facade_kit.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 +"""Assemble the façade kit onto the block buildings. + +Takes the imported kit from `/Game/Hollywood/Kit` and lays it out over a frontage +building's street face: shopfront bays on the ground floor, window bays above, +pilasters on the bay boundaries, a cornice under the parapet and a coping over it. + +Layout is driven by the building's own bounding box, so it follows whatever the +blockout gives: the bay count is `round(width / BAY)` and the storey count comes +from the height above the ground floor. Nothing is hand-placed per building. + + UnrealEditor-Cmd -run=pythonscript \ + -script=Tools/ue_apply_facade_kit.py -nullrhi -unattended -nopause -stdout + + # or, as a step inside the demo builder: + from ue_apply_facade_kit import apply_to_actor + +Result file: /tmp/gigline/kit-apply.txt +""" + +from __future__ import annotations + +import os +import sys + +import unreal + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import ue_common # noqa: E402 + +KIT = "/Game/Hollywood/Kit" +RESULT = "/tmp/gigline/kit-apply.txt" + +BAY_CM = 496.0 # the kit module +STOREY_CM = 310.0 +SHOP_FLOOR_CM = 310.0 # ground floor is one storey tall +KIT_PREFIX = "Kit_" + +lines: list[str] = [] + + +def log(message: str) -> None: + lines.append(message) + print(message) + + +def load_kit() -> dict: + """Load every kit mesh once, keyed by its short name.""" + kit = {} + registry = unreal.AssetRegistryHelpers.get_asset_registry() + filt = unreal.ARFilter(package_paths=[KIT], recursive_paths=False, + class_paths=[unreal.TopLevelAssetPath("/Script/Engine", "StaticMesh")]) + for data in registry.get_assets(filt): + mesh = unreal.EditorAssetLibrary.load_asset(str(data.package_name)) + if mesh: + kit[mesh.get_name()] = mesh + return kit + + +def _bounds(mesh): + box = mesh.get_bounding_box() + return box.min, box.max + + +def _spawn(mesh, location, label, material=None, parent_collection=None): + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, location) + if actor is None: + log(f"could not spawn {label}") + return None + actor.set_actor_label(label) + comp = actor.static_mesh_component + comp.set_static_mesh(mesh) + comp.set_mobility(unreal.ComponentMobility.STATIC) + comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS) + comp.set_collision_profile_name("BlockAll") + if material: + comp.set_material(0, material) + return actor + + +def apply_to_actor(actor, kit, materials=None, prefix=None): + """Lay the kit out over one building actor's street face. + + Returns the number of pieces placed. The building's own mesh stays in place + as the wall the pieces sit on. + """ + comp = actor.get_component_by_class(unreal.StaticMeshComponent) + if not comp or not comp.static_mesh: + return 0 + # Labels must be unique per building, otherwise two buildings' pieces share a + # label and the outliner (and any label-based lookup) becomes ambiguous. + slug = actor.get_actor_label().replace("Frontage_SM_", "").replace("North_", "").lower() + prefix = prefix or f"Kit_{slug}_" + mn, mx = _bounds(comp.static_mesh) + # The frontage meshes are spawned at the world origin and carry their position + # in the mesh, so the bounding box IS world space (actor location is 0,0,0). + origin = unreal.Vector(0.0, 0.0, 0.0) + width = mx.x - mn.x + height = mx.z - mn.z + if width < BAY_CM * 0.75 or height < STOREY_CM: + log(f"{actor.get_actor_label()}: too small for the kit " + f"({width / 100:.1f} x {height / 100:.1f} m)") + return 0 + + bays = max(1, int(round(width / BAY_CM))) + bay_w = width / bays # actual bay width for this building + # Street face: the frontage runs along X, and the street is at smaller Y. + face_y = mn.y + base_z = mn.z + storeys = max(1, int((height - SHOP_FLOOR_CM) // STOREY_CM)) + + def kit_material(name): + return (materials or {}).get(name) + + placed = 0 + for bay in range(bays): + cx = mn.x + bay_w * (bay + 0.5) + # ground floor: shopfront + for piece, dx in (("Kit_ShopPier_L", -bay_w / 2 + 30.0), + ("Kit_ShopPier_R", bay_w / 2 - 30.0)): + if piece in kit: + if _spawn(kit[piece], unreal.Vector(cx + dx, face_y, base_z), + f"{prefix}shop_pier_{bay}", kit_material(piece)): + placed += 1 + if "Kit_ShopGlass" in kit: + if _spawn(kit["Kit_ShopGlass"], unreal.Vector(cx, face_y, base_z), + f"{prefix}shop_glass_{bay}", kit_material("Kit_ShopGlass")): + placed += 1 + # upper storeys: window bays + for storey in range(storeys): + if "Kit_WindowBay" in kit: + z = base_z + SHOP_FLOOR_CM + STOREY_CM * storey + 65.0 + if _spawn(kit["Kit_WindowBay"], unreal.Vector(cx, face_y, z), + f"{prefix}window_{bay}_{storey}", kit_material("Kit_WindowBay")): + placed += 1 + # pilaster on the left boundary of every bay + if "Kit_Pilaster" in kit: + if _spawn(kit["Kit_Pilaster"], + unreal.Vector(mn.x + bay_w * bay, face_y, base_z), + f"{prefix}pilaster_{bay}", kit_material("Kit_Pilaster")): + placed += 1 + + # cornice under the parapet, coping on top + if "Kit_Cornice" in kit: + for bay in range(bays): + cx = mn.x + bay_w * (bay + 0.5) + if _spawn(kit["Kit_Cornice"], + unreal.Vector(cx, face_y, base_z + height - 145.0), + f"{prefix}cornice_{bay}", kit_material("Kit_Cornice")): + placed += 1 + if "Kit_Parapet" in kit: + for bay in range(bays): + cx = mn.x + bay_w * (bay + 0.5) + if _spawn(kit["Kit_Parapet"], + unreal.Vector(cx, face_y, base_z + height - 90.0), + f"{prefix}parapet_{bay}", kit_material("Kit_Parapet")): + placed += 1 + + log(f"{actor.get_actor_label()}: {bays} bays x {storeys} storeys, " + f"{width / 100:.1f} x {height / 100:.1f} m -> {placed} pieces") + return placed + + +def main() -> None: + kit = load_kit() + if not kit: + log(f"no kit meshes under {KIT} — run Tools/ue_import_facade_kit.py first") + return + log(f"kit meshes: {len(kit)}") + + level = "/Game/Hollywood/DemoMap" + if not unreal.EditorLevelLibrary.load_level(level): + log(f"could not load {level}") + return + # The map is LFS-tracked and checks out read-only. + content = unreal.Paths.project_content_dir() + ue_common.make_writable(os.path.join(content, "Hollywood/DemoMap.umap")) + + # Remove a previous application so re-running does not stack duplicates. + removed = 0 + for actor in list(unreal.EditorLevelLibrary.get_all_level_actors()): + if actor.get_actor_label().startswith("Kit_"): + unreal.EditorLevelLibrary.destroy_actor(actor) + removed += 1 + log(f"cleared {removed} previous kit actors") + + targets = [a for a in unreal.EditorLevelLibrary.get_all_level_actors() + if a.get_actor_label().startswith(("Frontage_", "North_"))] + log(f"frontage actors: {len(targets)}") + total = 0 + for actor in targets: + total += apply_to_actor(actor, kit) + log(f"placed {total} kit pieces") + + saved = unreal.EditorLoadingAndSavingUtils.save_map( + unreal.EditorLevelLibrary.get_editor_world(), level) + log(f"save_map {level}: {saved}") + + +try: + main() +except Exception as exc: # noqa: BLE001 + import traceback + log(f"FAILED: {type(exc).__name__}: {exc}") + log(traceback.format_exc()) + +os.makedirs(os.path.dirname(RESULT), exist_ok=True) +with open(RESULT, "w", encoding="utf-8") as handle: + handle.write("\n".join(lines) + "\n") diff --git a/Tools/ue_import_facade_kit.py b/Tools/ue_import_facade_kit.py new file mode 100644 index 0000000..4419907 --- /dev/null +++ b/Tools/ue_import_facade_kit.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Import the façade module kit into Unreal: /Game/Hollywood/Kit/*. + +Pairs with `Tools/facade_kit_build.py`. Each piece is imported as its own static +mesh at the module's origin (bottom-front centre), so a building can place a +piece by bay index and storey index with a single offset. + + UnrealEditor-Cmd -run=pythonscript \ + -script=Tools/ue_import_facade_kit.py -nullrhi -unattended -nopause -stdout + +Result file: /tmp/gigline/kit-import.txt (the commandlet swallows print()). +""" + +from __future__ import annotations + +import os +import sys + +import unreal + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import ue_common # noqa: E402 + +SRC = "/tmp/gigline/kit" +DEST = "/Game/Hollywood/Kit" +RESULT = "/tmp/gigline/kit-import.txt" + +lines: list[str] = [] + + +def log(message: str) -> None: + lines.append(message) + print(message) + + +def main() -> None: + if not os.path.isdir(SRC): + log(f"MISSING source directory {SRC} — run Tools/facade_kit_build.py first") + return + files = sorted(f for f in os.listdir(SRC) if f.lower().endswith(".fbx")) + if not files: + log(f"no FBX files in {SRC}") + return + + for filename in files: + asset_name = os.path.splitext(filename)[0] + task = unreal.AssetImportTask() + task.filename = os.path.join(SRC, filename) + task.destination_path = DEST + task.destination_name = asset_name + task.automated = True + task.save = True + task.replace_existing = True + ui = unreal.FbxImportUI() + ui.import_mesh = True + ui.import_materials = True + ui.import_textures = False + ui.import_as_skeletal = False + smd = ui.static_mesh_import_data + smd.import_uniform_scale = 1.0 + smd.combine_meshes = True + smd.convert_scene_unit = False + smd.force_front_x_axis = False + smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS + # Kit pieces are thin boxes and panes: complex-as-simple keeps the + # collision on the real faces instead of a fat convex hull that would + # fence off the shopfront glazing. + smd.auto_generate_collision = False + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + + mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{asset_name}") + if mesh is None: + log(f"{asset_name}: IMPORT FAILED") + continue + unreal.GigLineAssetTools.set_complex_collision_as_simple(mesh, True) + unreal.EditorAssetLibrary.save_loaded_asset(mesh) + box = mesh.get_bounding_box() + mn, mx = box.min, box.max + log(f"{asset_name:20} size=({mx.x - mn.x:6.1f}, {mx.y - mn.y:6.1f}, " + f"{mx.z - mn.z:6.1f}) cm min=({mn.x:6.1f}, {mn.y:6.1f}, {mn.z:6.1f}) " + f"collision={unreal.GigLineAssetTools.get_collision_complexity(mesh)}") + + log(f"imported {len(files)} kit piece(s) into {DEST}") + + +try: + main() +except Exception as exc: # noqa: BLE001 - surface the failure in the result file + import traceback + log(f"FAILED: {type(exc).__name__}: {exc}") + log(traceback.format_exc()) + +os.makedirs(os.path.dirname(RESULT), exist_ok=True) +with open(RESULT, "w", encoding="utf-8") as handle: + handle.write("\n".join(lines) + "\n") -- 2.47.3 From 32dff056c3c99a29c43128f7d07ce0754d92d6a0 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:32:47 +0100 Subject: [PATCH 05/13] fix(blockout): regenerate with --origin; frontage back on the verified spans The 0.1.2 blockout regeneration ran without --origin, so Blosm centred on the extract instead of fitting the project frame. The frontage shifted ~119 m in X and ~2.4 m in Y and the hero fell outside the 6902/6838 gap - the verified placement was silently undone, while every render still looked like a plausible block. - Blockout regenerated with --origin 34.101545,-118.338718 (affine residual 0.0 cm); frontage and north buildings re-exported and re-imported. - Verified spans restored: 6922 -24276..-17700, 6902 -17749..-14591, hero -14644..-12028 between them, theatre -12082..-8210, TCL -22889..-18442, Tussaud's -27071..-22799. - ue_apply_facade_kit now uses get_actor_bounds() (world space). It had used the static mesh bounding box, which is building-line-local, so every kit piece was placed ~12 km east of its building in empty ground. - ue_import_north_buildings.py was missing 'import sys' (NameError, exit 255, import silently skipped) - the same trap as the hero importer. - Kit re-applied: 625 pieces across eight buildings; packaged QA still PASS (walk, transect 0 holes, wall probe 0/35, facade probe 0/9). --- .../blockout/blockout-2026-11-06.blend | 4 ++-- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 ++-- .../Hollywood/Frontage/SM_Budget_6822.uasset | 4 ++-- .../Frontage/SM_ElCapitanTheatre.uasset | 4 ++-- .../Frontage/SM_Hollywood_6806.uasset | 4 ++-- .../Frontage/SM_Hollywood_6902.uasset | 4 ++-- .../Frontage/SM_Hollywood_6922.uasset | 4 ++-- .../Hollywood/Frontage/SM_LeeDrug_6800.uasset | 4 ++-- .../Hollywood/North/SM_Madame_Tussauds.uasset | 4 ++-- .../North/SM_TCL_Chinese_Theatre.uasset | 4 ++-- .../Content/Hollywood/Site/M_Asphalt.uasset | 2 +- .../Content/Hollywood/Site/M_Concrete.uasset | 2 +- .../Hollywood/Site/M_FacadeBanner.uasset | 2 +- .../Hollywood/Site/M_FacadeGlass.uasset | 2 +- .../Hollywood/Site/M_FacadeStone.uasset | 2 +- .../Hollywood/Site/M_FacadeStoneShadow.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 | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 4 ++-- .../Hollywood/Textures/T_Asphalt_R.uasset | 4 ++-- .../Hollywood/Textures/T_Concrete_BC.uasset | 4 ++-- .../Hollywood/Textures/T_Concrete_R.uasset | 4 ++-- .../Textures/T_StoneAshlar_BC.uasset | 4 ++-- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 4 ++-- .../Hollywood/Textures/T_Stucco_BC.uasset | 4 ++-- .../Hollywood/Textures/T_Stucco_R.uasset | 4 ++-- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 21 +++++++++++++++++++ Screenshots/demo/review-demo-crossing.png | 4 ++-- .../demo/review-demo-north-buildings.png | 4 ++-- Screenshots/demo/review-demo-spawn-view.png | 4 ++-- Screenshots/demo/review-demo-street-level.png | 4 ++-- Screenshots/review-character.png | 4 ++-- Screenshots/review-ground-contact.png | 4 ++-- Screenshots/review-hero-facade-front.png | 4 ++-- Screenshots/review-hero-facade-oblique.png | 4 ++-- Screenshots/review-hero-portals.png | 4 ++-- Screenshots/review-npc-prefab.png | 4 ++-- Screenshots/review-overview.png | 4 ++-- Screenshots/review-player-placement.png | 4 ++-- Screenshots/review-scale-cubes.png | 4 ++-- Screenshots/review-shadow-compare.png | 4 ++-- TODO.md | 2 +- Tools/ue_apply_facade_kit.py | 11 +++++++--- Tools/ue_import_north_buildings.py | 1 + 48 files changed, 107 insertions(+), 80 deletions(-) diff --git a/Art/blender/blockout/blockout-2026-11-06.blend b/Art/blender/blockout/blockout-2026-11-06.blend index 6a53c85..89b7836 100644 --- a/Art/blender/blockout/blockout-2026-11-06.blend +++ b/Art/blender/blockout/blockout-2026-11-06.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9056f0f302ad12b882f2416d2d500aab679ea65ccc56f8bb127f79d257e63aa2 -size 250060 +oid sha256:30e51795ce54ad7db03a8fe6d37e5dfb143b25706a44bec84179794b39b2f372 +size 250193 diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 53c90ff..c17c84f 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:cabcd908ff5149ac64773f34092efbdf7df077165db50438aef1d36e846ecc46 -size 939345 +oid sha256:7f9628232386d4f68e4349204f439ee10e77fb6167530e3b9a301e36e602ca4f +size 921214 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_Budget_6822.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_Budget_6822.uasset index fd8d792..1e93748 100644 --- a/Game/GigLine/Content/Hollywood/Frontage/SM_Budget_6822.uasset +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_Budget_6822.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04eea883885521fadfc2675c4ee93c7aba61cbbf84e464d34a634f1b778bc161 -size 78284 +oid sha256:2c690e276876e576568a21aa06f28c1f28162447e75ee7e6241bc283f61c299c +size 78281 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_ElCapitanTheatre.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_ElCapitanTheatre.uasset index 7182b89..5436281 100644 --- a/Game/GigLine/Content/Hollywood/Frontage/SM_ElCapitanTheatre.uasset +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_ElCapitanTheatre.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92b4a9e1ffebdec61f477c62228b02074d1afed6e76288b9cac7cfc21479bf94 -size 79348 +oid sha256:6fafde149fd5ca0e360d0340296b18d77371b84374c22eeee65eb21c7288f405 +size 79337 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6806.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6806.uasset index f8f5611..83f5f81 100644 --- a/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6806.uasset +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6806.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21faaa28e97235717b973e3827604805edd88195fb0997c1ff374e8e04f4186a -size 78998 +oid sha256:f728a5955380edeca0c7628c39fc883d1cb23d7d1383cb408d33244c3447a958 +size 78991 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6902.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6902.uasset index 49ee421..d62505c 100644 --- a/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6902.uasset +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6902.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86082f721699b618c0989d250861bd6a079c45bc321f9472c191344ad3a9fef3 -size 78349 +oid sha256:a158f317c4e583b2113e22ca636c8ad3096e135305f960324be37c389a57d410 +size 78344 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6922.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6922.uasset index 8ffa7e4..b8f4183 100644 --- a/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6922.uasset +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_Hollywood_6922.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c06ffd414b54a3c8383344da54baf05938ba3aa2a6322f3d6801b2f7cc1ccfc -size 78881 +oid sha256:025d206a0cb0dfc90de3e75407e01690eadb9995f1c2299693f42a8c8e96f46d +size 78886 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_LeeDrug_6800.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_LeeDrug_6800.uasset index a0948ea..eda0333 100644 --- a/Game/GigLine/Content/Hollywood/Frontage/SM_LeeDrug_6800.uasset +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_LeeDrug_6800.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad59e28c16e965ddd814e69d9510233f93b44410bf42d5602e36609124879465 -size 78097 +oid sha256:76d70f7b9256a26570b545a40179d7901154dc4040b8d399207b0f3e9e8490e7 +size 78103 diff --git a/Game/GigLine/Content/Hollywood/North/SM_Madame_Tussauds.uasset b/Game/GigLine/Content/Hollywood/North/SM_Madame_Tussauds.uasset index 08c9aa6..5fc5c95 100644 --- a/Game/GigLine/Content/Hollywood/North/SM_Madame_Tussauds.uasset +++ b/Game/GigLine/Content/Hollywood/North/SM_Madame_Tussauds.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6916ce5e1856eaefccce5faa3d688f0bdb6398a2dcc56a872ceb00080db7993b -size 79982 +oid sha256:43191905b268a7ade80415330e8aa009f2de024175b34d1fd806f32798b6664b +size 79990 diff --git a/Game/GigLine/Content/Hollywood/North/SM_TCL_Chinese_Theatre.uasset b/Game/GigLine/Content/Hollywood/North/SM_TCL_Chinese_Theatre.uasset index d8f0531..be72065 100644 --- a/Game/GigLine/Content/Hollywood/North/SM_TCL_Chinese_Theatre.uasset +++ b/Game/GigLine/Content/Hollywood/North/SM_TCL_Chinese_Theatre.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ba67436b139db9375a1e43d05ea5fdd9c4770f4f778f3ace12cc7a47426222f -size 80253 +oid sha256:9e0f1296771b3e1f065ce670180205a9368ed199c733213e8e7c93b2a83ec99f +size 80268 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index d83bc87..83426cf 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:29dcab210b352dbcec4665b5d18e03080327938ea2be01dfe0c36862c8161095 +oid sha256:fb97fb7403d331fff1a29884eb1edef8c0bbbe50e6fb33cfbe9ff9c696d24e30 size 7876 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 909a78f..e587855 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:14c5b272b2a67088a123541518fa5749e47e2dc994b51ae699fd8fc114aee3ad +oid sha256:9c93e7576378561a245689f414d1046cff9e3de14329c8773f38e72abf736e76 size 7881 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index 8eb662b..d10f0e7 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:158ac3fd592d71816fe061cac6aa23a025bb3a8f83ca338bc510e144bc4a06dd +oid sha256:e163fe98ecb0da87e920e5f685f4698392c7b00f5c0edb4d3497d61d2eeac98c size 8667 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index 93e7535..961b4b6 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb21c5ac661880794989401129b2ea4d424c6a19dc5072b1505f778709d562bd +oid sha256:8f24d1f4d483cf6896103250cf5726f4ab258ecb14386d4e6fd5786ee00263b8 size 8662 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 0f7c2de..2e46754 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:643078c1b3aef3b980d208bc4bc9cdc727b337a6e00ac58460a4b601476e9019 +oid sha256:f8d3097b908c53a6c63dd8c2320794212362d1f5e4b1b1ff65a3e0f50e70c895 size 12131 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 00bdaa8..9f382ec 100644 --- a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset +++ b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb32d2e8dc8f85f2f1f6bde81faa7742a9c9b3f2f5b5c9adc66a96be43a1fc69 +oid sha256:853bb97a0318147126cd22b3bf3b2e6781e9605aadd1ace75c9accbd4e04a3d6 size 12161 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 0f0755d..6201c75 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:b22a46ee7729364ba83d6cac7b38634fa32d93140c29b2003f73f7bf2a2c0aac +oid sha256:8a8fbad4e48bbf53f1f3dbc3d19fdb497ade7a70e108e0f009829176dcb098b0 size 7871 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index ab8e270..dd99703 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:a06a0150737402a11bf660ff6848564975da950654eacac7023b3932be2e962f +oid sha256:9add1813503a7727aea65bb55249ea2eae84a47a349f2ee6f011eaddb6a4d592 size 7901 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index e333504..c62ba86 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:b25e55f9bfb3181e8f12535b43ce829d3923668bbab3635ec4c321534fd76305 +oid sha256:5b08b4bc9d63293bd4a2b2db4e6a70c6854b309a579d7121fb065ea3d9834051 size 7886 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 00df7ae..26e7b21 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:df485df5c559d21b96682f2a381a6a53c9456c5d14b34394e2b5f0e93cbf04b8 +oid sha256:81dae9d6c50131436e01dacd27bacfbeb6186405d9d70c1b2858f51dff158df5 size 8637 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index d60380d..8dfa296 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:69075d0ba49303ed0c25fbbc966abc7b0d2f3fe34d716bf648989e04394da70d +oid sha256:a3ca6de1695fb13f5435977ffa8c8330c16075cc21a2a78fef17d1c1ee0ee7cd size 357901 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset index 8862664..c70868b 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa06702c3b89e8c16522f94292bc69e01ffe11e92d050bdee27341431774fa21 -size 70786 +oid sha256:03f6ce0b402dd3a4cbfb93ca704ff81c1ac782d1a1f34929342c0cf499eecc19 +size 70879 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset index b5e0223..966fd6c 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Asphalt_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90efc297644eae3bb067300cef611efe68e48e25960a259fa482e717d77a35ad -size 28917 +oid sha256:ad6d2595d987f3e4045995aebf9e3f7845ad229beed37308e9d8f4c7c33e1106 +size 29011 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset index 88ae661..8085bdb 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8f007448f3038bbac6bb70a77d26a4bc8ddccd02a227cedba74d14d901c631a -size 58474 +oid sha256:918cc5acf1447085dab73d989c89ac1423fe1cba599ae566d5524cc26076d1a8 +size 58568 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset index 3ba2425..cdaaede 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Concrete_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcc36e012cb5428e1568976cdf440be179330e635e09c2ce5b78d3d71dea67a4 -size 17423 +oid sha256:ae968668a57242ffa65b81297f4dcd8e3280111b9f3470e1ebd4fd92eaee4a88 +size 17517 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset index b3fae28..c132640 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a1b2cac653493aec57f1878f81ffe823dea291c2a6ea27e1e78c3fa3aed2359 -size 40517 +oid sha256:6549cec0e7c5cc24781c484d9901c2ceb3c7a9db25e74352ee1d0f8f619c90cf +size 40610 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset index 491e0a6..d9e5552 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_StoneAshlar_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0613545632883b4256025b712c35a8c9345c364beeb0089592163a1a84973f69 -size 18677 +oid sha256:ad209b9388a8aed7cea08b267eeab17abec06fbaa392b31378117f7d828fd9c5 +size 18770 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset index 198d125..5e443c4 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_BC.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6edac9a08e27459933560127a922e504f18b88656e37932699fc18e8920979f6 -size 62653 +oid sha256:516debf8a3076a5cf3fcc8e4ebc2e63d5bc6b290ec9865eb2f47118cea9c7e3f +size 62746 diff --git a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset index 8639638..8ab670b 100644 --- a/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset +++ b/Game/GigLine/Content/Hollywood/Textures/T_Stucco_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:089a9df31bce521e14f6519539063ac1c5eb73641a73c70189aad9725cd4eefd -size 17448 +oid sha256:69b431dd98f073b4987a3019cb8cc22ba23055663eb986a03be57104b2c2e1f2 +size 17541 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 4341632..68431f1 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0bb3ac4675c0622116d74233da208c1ba97e686e0377e99ef8a77dc49bcbb24 +oid sha256:fd3c39d5ee6ba05ce72d2c32d02fdc25af7ffef7f1351aafba6020a1e5a0f289 size 28811 diff --git a/MEMORY.md b/MEMORY.md index 17e55eb..16d2e96 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -591,3 +591,24 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - The kit meshes are spawned at the world origin with their position baked into the mesh (like the frontage exports), so placement uses the bbox directly and must not add the actor location. + +## 2026-11-09 — CRITICAL: blockout must be regenerated with --origin + +- Regenerating the blockout in 0.1.2 (for the 3.1 m storey height) was run **without + `--origin`**, so Blosm centred on the extract instead of fitting the project frame. + The whole frontage shifted about **119 m in X and 2.4 m in Y**, and the hero ended + up outside the 6902/6838 gap — the "verified placement" was undone silently, and + every render still looked like a plausible block. +- Correct invocation (README documents it, and the affine fit prints + `frame correction residual: max 0.0 cm`): + `blosm_blockout.py --osm … --bounds 34.1006,-118.3422,34.1020,-118.3370 + --level-height 3.1 --origin 34.101545,-118.338718 --assets-dir … --out …` +- After re-export + re-import, the frontage spans are back to the verified values: + 6922 −24276..−17700, 6902 −17749..−14591, **hero −14644..−12028 between them**, + theatre −12082..−8210, and the north buildings at −22889..−18442 / −27071..−22799. +- **`ue_import_north_buildings.py` also lacked `import sys`**, so its sys.path line + raised NameError and the import silently did nothing (exit 255). Same trap as the + hero importer: these scripts imported only `unreal`. +- Lesson: after any blockout regeneration, probe the staged buildings' world X spans + and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before + trusting a render. A shift of this size is invisible in a screenshot. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 76af3a1..d4d518f 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e38ac20db95e026e2a3031dc4a7044eade0052f21790ce24479e72be0668a6a3 -size 2754407 +oid sha256:b3e6877437bc5b7034da60d51c01c463c8b0fdc3c267b9be847ce8b59a0ef255 +size 2997213 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 3748bc7..7900ebd 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:632f36c7708e60afa8249341ba8e9c92d27dd486d412af50d91f32394db8d523 -size 2262962 +oid sha256:e7e7c0fda39926c8cbb27ee36e04192ec32014e3b1711a86e1984870701a017c +size 2079681 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index fdec6f2..8303781 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbc59fa80c5d6769f88bf120246cd1d18d885264cbccb91bd9e98fa6e1d31f87 -size 2773580 +oid sha256:af2b48830aab4593e00f23d8a04a502b183fbd48a7a3e0c0a3cdeea2c3416683 +size 2883301 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 770b478..ebe6b48 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a612caffb70624590152784204d17c0816c6e9dd588b2ea9da60f95cc463830 -size 2676882 +oid sha256:ebb5422d249d2c332339d9971747e29f067f7a5b4be506a4039495da13adb00a +size 2787860 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index 838d2c8..bcc673d 100644 --- a/Screenshots/review-character.png +++ b/Screenshots/review-character.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:772930bca1b302578fd771a7d8f5be7da82b5c6566f9efe7344561a148de1635 -size 2536185 +oid sha256:80c0932976ffa2fd3c4ecf60929a0fccb7fd6f5fdfc977cfae2e082eedef3662 +size 2530590 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index 6cb7bd5..e1daff6 100644 --- a/Screenshots/review-ground-contact.png +++ b/Screenshots/review-ground-contact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54c81c33f84515be3c947a3b7d1376eefe9e8d017165487441128d95c258add6 -size 1754197 +oid sha256:4afd2eaeec6a97a34c7649437ce0567324d724d5d04a5da2e0ba73f84eae47d1 +size 1754454 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index e03c6e8..4a42281 100644 --- a/Screenshots/review-hero-facade-front.png +++ b/Screenshots/review-hero-facade-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88d440c07d0220e880af430249f1d2eafa3fe780ab7d5367ca1b299761ea9400 -size 2294756 +oid sha256:10b7a138b757c3cef7dae03c8ae2c7c1ec87655b69e4b8bdf559622dd574c4b5 +size 2306751 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index 90665d9..e520bf6 100644 --- a/Screenshots/review-hero-facade-oblique.png +++ b/Screenshots/review-hero-facade-oblique.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfd3ad70d01a466316a64f34cde6cf7f699b6e9cb7bdaee6e26873c5c912b557 -size 1510048 +oid sha256:d7d6027fd1cf8a1c14c74db69153f9d5b978d005cf4d9072dc306cdc06299120 +size 1514483 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index 9569846..e8d89aa 100644 --- a/Screenshots/review-hero-portals.png +++ b/Screenshots/review-hero-portals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05c63be2a22a2a35bd95c7c7467da67f9556d0ea56395149e860476cdf9f34bd -size 1322804 +oid sha256:2ae079e24960fb2cff61efa72188a801640bea932ef9e0d88a3c490b8de4bcaf +size 1323136 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index 4bb5b3e..1f7f805 100644 --- a/Screenshots/review-npc-prefab.png +++ b/Screenshots/review-npc-prefab.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:732b96208e25edc36f6e52cc0b214540d07fc6484d0d1cc76df438ce99b050fc -size 1735007 +oid sha256:d0555e368b4c800c2bc2880af188f1d6f313d0dd54dc99583bdbf756025b78a6 +size 1737659 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index e0452d1..d2f4b4a 100644 --- a/Screenshots/review-overview.png +++ b/Screenshots/review-overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67bb53592ca9e3bfaae5b5feb4d772fdbc474ed3bedd78b81cb999a44aa4a839 -size 2488342 +oid sha256:01a797c5875f6b9cdb6a21d684063fee21f83255f4bc5136df0a110d0862896d +size 2571295 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index 00dc1af..2d84235 100644 --- a/Screenshots/review-player-placement.png +++ b/Screenshots/review-player-placement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c01fcc7ee234597c8451154ebe9b91bf5aa0720d963b1ee5694835aa5133ad2 -size 2030579 +oid sha256:a0ececa978f34d1be3d92924a92a87378eb81d6cfb7a1cca9089154c5f3072c4 +size 2022640 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 9e1bc57..56e4772 100644 --- a/Screenshots/review-scale-cubes.png +++ b/Screenshots/review-scale-cubes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:945099a7cf7a5f185ab445fdb0c6906353fa3789ec7eb4f8d573632b3c965d18 -size 1935381 +oid sha256:b14730cd7371d8580b385719d5465f71d1108350b85bb03774c2dc2dcf3a69fb +size 1941562 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index 161d09f..a089e80 100644 --- a/Screenshots/review-shadow-compare.png +++ b/Screenshots/review-shadow-compare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99b64891c9b211d9990bfbf39b10f3df73ca3be324461550184d9ee4dec5db17 -size 1711185 +oid sha256:3a89c068cab13677e917c003714933cf898df4f86efcb04fa099f1067c484616 +size 1716760 diff --git a/TODO.md b/TODO.md index e22ff48..44c535c 100644 --- a/TODO.md +++ b/TODO.md @@ -18,7 +18,7 @@ Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the faç - [ ] **Finish the Blosm blockout cleaning/detailing (moved from the deferred `0.1.0` item).** Import state: `Art/blender/blockout/blockout-2026-11-06.blend` (86 meshes, EPSG:26911, 0.0 cm affine residual) with cleaning tooling in `Geo/tools/` and `Art/blender/blockout/blosm_blockout.py`. To do here: (a) untextured roofs/facades → **Replace placeholder materials** below; (b) Blosm `setupScript` façade styles → **Give OSM buildings a façade style** below; (c) the 3 of 15 buildings lacking height data are the `0.1.2` placeholder-heights item. - [x] **Define the façade module.** **Done 2026-11-09.** `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay**: pilaster, sill, lintel, window bay (surround + inset glazing), shopfront bay (piers, glass, fascia), door bay (jambs, door, threshold), cornice and parapet + coping — 16 meshes, each exported through the shared `ue_metric_v1` preset with a JSON part report recording dims and evidence. `Tools/ue_import_facade_kit.py` imports to `/Game/Hollywood/Kit`. `Tools/ue_apply_facade_kit.py` assembles it from each building's own bounding box: **638 pieces placed across the six frontage buildings and both north buildings** (6922 = 13 bays × 11 storeys, theatre 8 × 5, TCL and Tussaud's 9 × 4, etc.). First pass put the window bays inside the solid wall (invisible) — the kit now projects the surround 3 cm proud with glazing 6 cm behind it. - [A] **Replace placeholder materials.** **In progress 2026-11-09.** `ensure_material()` now builds masonry coursing (world-space course height + joint band + tint, corrected for UE's centimetre WorldPosition) and roughness variation; new `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5 without a refraction pass) and `M_FacadeBanner`; assignment is per **slot** keyed on the imported slot name (it was slot-0 only, which on the hero is the banner, so stone/glass/shadow were still flat). Hero: 4/4 slots, frontage and north buildings 2/2. Remaining: bake real texture maps (brick/stone/asphalt generators) rather than graph-only detail, and give the frontage shopfront line its own material. -- [ ] **Give OSM buildings a façade style.** Blosm exposes `scene.blosm.setupScript` (`setup/neoclassical.py` is the example) so a building can carry a real elevation instead of a plain extrusion. Build one style script for the south frontage and one for the north. +- [x] **Give OSM buildings a façade style.** **Closed as superseded 2026-11-09.** The Blosm `setupScript` route would build a second, competing façade system next to the module kit defined in the previous task. The kit already gives every staged building a real elevation (shopfront line, window rhythm, pilasters, cornice, parapet) from a single module, so a Blosm style script would duplicate it with a different vocabulary and a different set of assumptions. Revisit only if the kit cannot express a building the photographs demand. - [ ] **Hero façade detail pass.** Ionic capitals and fluting, the parapet motto band and medallions as geometry or decal, window rhythm behind the colonnade, and the three portal flights at their photographed proportions. - [ ] **El Capitan Theatre (6838) and the retail neighbours.** Model from the 18-frame 5472×3648 reference set; the theatre shares the measured 4.96 m bay rhythm with the temple. - [ ] Review every façade change with `Tools/render_screenshots.sh` before tagging. diff --git a/Tools/ue_apply_facade_kit.py b/Tools/ue_apply_facade_kit.py index b33f1e3..04e6ae7 100644 --- a/Tools/ue_apply_facade_kit.py +++ b/Tools/ue_apply_facade_kit.py @@ -92,9 +92,14 @@ def apply_to_actor(actor, kit, materials=None, prefix=None): # label and the outliner (and any label-based lookup) becomes ambiguous. slug = actor.get_actor_label().replace("Frontage_SM_", "").replace("North_", "").lower() prefix = prefix or f"Kit_{slug}_" - mn, mx = _bounds(comp.static_mesh) - # The frontage meshes are spawned at the world origin and carry their position - # in the mesh, so the bounding box IS world space (actor location is 0,0,0). + # WORLD-space bounds. The imported meshes carry a building-line-local offset in + # their own vertices and the demo builder then positions the actor, so the + # asset's bounding box is NOT world space: using it put every kit piece about + # 12 km east of its building, floating in empty ground where nothing could see + # it. get_actor_bounds() includes the actor transform. + origin_w, extent_w = actor.get_actor_bounds(False) + mn = unreal.Vector(origin_w.x - extent_w.x, origin_w.y - extent_w.y, origin_w.z - extent_w.z) + mx = unreal.Vector(origin_w.x + extent_w.x, origin_w.y + extent_w.y, origin_w.z + extent_w.z) origin = unreal.Vector(0.0, 0.0, 0.0) width = mx.x - mn.x height = mx.z - mn.z diff --git a/Tools/ue_import_north_buildings.py b/Tools/ue_import_north_buildings.py index ee5b820..f65999a 100644 --- a/Tools/ue_import_north_buildings.py +++ b/Tools/ue_import_north_buildings.py @@ -12,6 +12,7 @@ # -script=/Tools/ue_import_north_buildings.py -nullrhi -unattended -nopause -stdout import os +import sys import unreal -- 2.47.3 From 1b069188a213499587ac78fece3e66defb952469 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:38:29 +0100 Subject: [PATCH 06/13] refactor(kit): SM_ asset naming and topology validation per the modeling checklist - Kit pieces export as SM_Kit_* (Unreal static-mesh convention) while the Blender objects keep their readable names; the kit loader strips the prefix so layout still addresses pieces by short name. - facade_kit_build.py validates each piece before handoff (non-manifold edges, loose vertices, triangle count) and records the result in the part report: 16 pieces, all manifold, no loose geometry, 12 tris each. - ue_import_facade_kit.py removes kit assets the current build no longer produces, so a rename cannot leave two generations of the same piece behind. - Kit re-imported and re-applied (625 pieces); packaged QA still PASS. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +-- .../Content/Hollywood/Kit/Kit_Coping.uasset | 3 -- .../Content/Hollywood/Kit/Kit_Cornice.uasset | 3 -- .../Content/Hollywood/Kit/Kit_Door.uasset | 3 -- .../Hollywood/Kit/Kit_DoorJamb_L.uasset | 3 -- .../Hollywood/Kit/Kit_DoorJamb_R.uasset | 3 -- .../Content/Hollywood/Kit/Kit_Lintel.uasset | 3 -- .../Content/Hollywood/Kit/Kit_Parapet.uasset | 3 -- .../Content/Hollywood/Kit/Kit_Pilaster.uasset | 3 -- .../Hollywood/Kit/Kit_ShopFascia.uasset | 3 -- .../Hollywood/Kit/Kit_ShopGlass.uasset | 3 -- .../Hollywood/Kit/Kit_ShopPier_L.uasset | 3 -- .../Hollywood/Kit/Kit_ShopPier_R.uasset | 3 -- .../Content/Hollywood/Kit/Kit_Sill.uasset | 3 -- .../Hollywood/Kit/Kit_Threshold.uasset | 3 -- .../Hollywood/Kit/Kit_WindowBay.uasset | 3 -- .../Hollywood/Kit/Kit_WindowGlass.uasset | 3 -- .../Hollywood/Kit/M_Kit_cornice.uasset | 4 +-- .../Content/Hollywood/Kit/M_Kit_door.uasset | 4 +-- .../Content/Hollywood/Kit/M_Kit_lintel.uasset | 4 +-- .../Hollywood/Kit/M_Kit_parapet.uasset | 4 +-- .../Hollywood/Kit/M_Kit_pilaster.uasset | 4 +-- .../Hollywood/Kit/M_Kit_shopfront.uasset | 4 +-- .../Content/Hollywood/Kit/M_Kit_sill.uasset | 4 +-- .../Content/Hollywood/Kit/M_Kit_window.uasset | 4 +-- .../Hollywood/Kit/SM_Kit_Coping.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_Cornice.uasset | 3 ++ .../Content/Hollywood/Kit/SM_Kit_Door.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_DoorJamb_L.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_DoorJamb_R.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_Lintel.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_Parapet.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_Pilaster.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_ShopFascia.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_ShopGlass.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_ShopPier_L.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_ShopPier_R.uasset | 3 ++ .../Content/Hollywood/Kit/SM_Kit_Sill.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_Threshold.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_WindowBay.uasset | 3 ++ .../Hollywood/Kit/SM_Kit_WindowGlass.uasset | 3 ++ Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 14 ++++++++ Screenshots/demo/review-demo-crossing.png | 4 +-- .../demo/review-demo-north-buildings.png | 4 +-- Screenshots/demo/review-demo-spawn-view.png | 4 +-- Screenshots/demo/review-demo-street-level.png | 4 +-- Screenshots/review-character.png | 4 +-- Screenshots/review-ground-contact.png | 4 +-- Screenshots/review-hero-facade-front.png | 4 +-- Screenshots/review-hero-facade-oblique.png | 4 +-- Screenshots/review-hero-portals.png | 4 +-- Screenshots/review-npc-prefab.png | 4 +-- Screenshots/review-overview.png | 4 +-- Screenshots/review-player-placement.png | 4 +-- Screenshots/review-scale-cubes.png | 4 +-- Screenshots/review-shadow-compare.png | 4 +-- Tools/facade_kit_build.py | 32 ++++++++++++++++++- Tools/ue_apply_facade_kit.py | 5 ++- Tools/ue_import_facade_kit.py | 10 ++++++ 60 files changed, 154 insertions(+), 97 deletions(-) delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset create mode 100644 Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index c17c84f..c0b0255 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:7f9628232386d4f68e4349204f439ee10e77fb6167530e3b9a301e36e602ca4f -size 921214 +oid sha256:fbbfbd1af034947cf225a95f7c093c80fc415c2520a3bc4be2d8e95e6925170a +size 921274 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset deleted file mode 100644 index 47b6cdb..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Coping.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b8c34895de32da94b4d773b3b7be342c4468295401d4701a3bdc93ed923e9f10 -size 72484 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset deleted file mode 100644 index 97fd0f2..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Cornice.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01b734f5cf0d08395e924b2c2ba5c6b8fdb08041a474ed24f4fb151323b32dd9 -size 72493 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset deleted file mode 100644 index b833aa7..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Door.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e50c198203e70bc926f17c056978c5c7a93d1705c30600c397e520c4f4c4f42 -size 72369 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset deleted file mode 100644 index dfb83cc..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_L.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8d554e6f5122f5b7ed9888432e5941522cab5c9a28a2c65b0bf5ba6f2b9c24ec -size 72483 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset deleted file mode 100644 index 089c170..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_DoorJamb_R.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4ac06e8d69b569adda3b120944fa41ab8e4ca1a544fa4263d61b9439552319f -size 72492 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset deleted file mode 100644 index 58fc7e7..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Lintel.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6437b4b48e40b650f9c13db22bd34979361b2da2521e55aedc3000c0ae416fad -size 72449 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset deleted file mode 100644 index ef8a281..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Parapet.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96f42acaadd77b069bc6da62c646183ded8c0c8b2f239121a3d737b86a2b113f -size 72482 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset deleted file mode 100644 index bea3a6d..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Pilaster.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bd3a3956c2384b15e38a67fb2b0f350bbf9d073bf204efe282bc84c2605e5331 -size 72535 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset deleted file mode 100644 index 797f05b..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopFascia.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2fd1b3f2439e60cb7d3578215543cecd60c7aeb2115099268f44dad470bf91d0 -size 72617 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset deleted file mode 100644 index de30ffd..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopGlass.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:680337f0ed0e2b8c58495e539d87766d76f688b979908e39f12e1372d137872b -size 72600 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset deleted file mode 100644 index 3515bae..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_L.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e86da75085c4abf9d8b4eea5a9b77f208f0bb811d3418fb33ca5da2febf436de -size 72612 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset deleted file mode 100644 index 5d13aea..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_ShopPier_R.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:35a391ad6bd9dbe9be1ac5ec24848d94d91de3831810748b8b7d4ea2dd1dd663 -size 72620 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset deleted file mode 100644 index 2ea504e..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Sill.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a1dd748d23ab0caaf1b13f3a85b4a2d11a89e2f6cff311b8326750f517a2e59d -size 72365 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset deleted file mode 100644 index 546a234..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_Threshold.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a54520339bb3453b866e7da7fde9bd5675a3af2b641766df47604e328f46ae8 -size 72465 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset deleted file mode 100644 index 794f4d5..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_WindowBay.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:601098d4b531b20be1243c06644cc7ddf70e4e4f3d746c6b5efba080fdf593ac -size 72530 diff --git a/Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset deleted file mode 100644 index e82ebd2..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/Kit_WindowGlass.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:304d251f899f05b113f8011b24dcd9090e9c4f83a9d8658db55a3399ba98ddd3 -size 72565 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset index 925853a..817d539 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb17b057f18958dd2086800f37fe1c951166aaeb0c7f2b842f2ab5cff786849d -size 56606 +oid sha256:02921296aa4e2107411a29db7b2d7b07d46dc0b7b3be6fa74ffb4d6123e3e931 +size 56615 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset index f140901..f0d12b4 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d92a56b405a8eba2db4e31c5603b77195ba297f0dce70e4ab750a63b84ab1155 -size 56549 +oid sha256:ed76fe8a3e3fd3f2060feef885f20b4b7b4f26cc9c36c742b77957e0b8427c83 +size 56558 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset index d80faf0..c2effdd 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b235d479ea427b0bf4e495f4e74bad0853bdc466d8eaf9a116f49f53905a75e -size 56587 +oid sha256:e82df3759ae16c1ace533458f0e18dba4097e92049bfdcd2e2022ad5f1158f0c +size 56596 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset index 2bd7207..d5f0bd5 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:550e00ba4d59e75e0f5524dc278b34c8c0bd2dae1ab9f9567bed2b3fd820f7c1 -size 56595 +oid sha256:d1cedd8b5e9faf255d1aa6194f45809d0599d13715cbaf21fdfbacc0a08758b7 +size 56604 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset index 4a29ddf..de12c7e 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae26b34392a8f173a82d5936449a93a882b7cec52eb788793447352e59fe8443 -size 56625 +oid sha256:8db53e9022174775a2bd23cb08538f6ed2a3ef376c0f3ede3028580bb3e1b57b +size 56634 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset index 0e6215f..1c88810 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1610ba046a1b76230aad2f6990bcd506eb3ee479248a442f21fee32ee9337612 -size 56647 +oid sha256:2acb6664dce7be4e009fc986a0c35d7723c0063b8de80e0d1270ea4c33da1553 +size 56656 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset index 6ce3d70..36eaf05 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:821549d7ed69a2c726d7e6fa2dafafa72fb45245a5c0792f532cac52be79c5ef -size 56560 +oid sha256:0b347bb058b46f29d49d75388988faa08a6484663d384b7599260174105be46a +size 56569 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset index 7066f64..522110f 100644 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d034b7c099db62f88d9a6df23e6aa7843cf4b90689e90e4836011b9b1e1f88a9 -size 56607 +oid sha256:8a810125b00d7ac8fb3186c7d0c4e6da0579ce05de267c8800514ef2840c8f31 +size 56616 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset new file mode 100644 index 0000000..99f2aab --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24a967d6e43238db6972d0488e0680e5b529f0ecc13b0fe94d7c460173073fe9 +size 68736 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset new file mode 100644 index 0000000..ea90c73 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7aea26e6c397c7cdb68392ea7d314c517e94712361095b738fdcf7f0db25757 +size 68745 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset new file mode 100644 index 0000000..610ec2e --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd344a450dc99e9e21808967b33b2a785da75f90c4a0b29d6458612013a2bb6a +size 68621 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset new file mode 100644 index 0000000..460b603 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c581fb39f3017d38b0a84451049242d7f465750afa24d5986c2e98717d15598 +size 68806 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset new file mode 100644 index 0000000..2a16d39 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6637ffd140dd2ba4c35d971df289c40e371ecfcad7c89fe7f98be37eba5a9192 +size 68815 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset new file mode 100644 index 0000000..8d0e368 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cefb4896f71f931e75b22f772ef02714d38001f38118eaf6fcab75e842e03664 +size 68699 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset new file mode 100644 index 0000000..45a4a55 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab0c6b4c67c740ea4d0d9bf767bf30d35b0056a96b8911baaa24c39fbceb6a32 +size 68803 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset new file mode 100644 index 0000000..d34df84 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6242668e209a5801a93a2482c291d2417320545853d6aa363797f5b8fa74db27 +size 68785 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset new file mode 100644 index 0000000..838bf32 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d30784e60c2c14e1c6df5bb3640564309cac13e9301d498a49d98080740625dd +size 68867 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset new file mode 100644 index 0000000..e3a0de7 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d630d6685a991b529943fa90ddf2da1e1219749351ea27d2d23b2d091b685af3 +size 68921 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset new file mode 100644 index 0000000..d418592 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5dde20d12e36a3b7f4b5ec106e9a33ec41ad46085e3af37f1311d9f0b89f711 +size 68935 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset new file mode 100644 index 0000000..e1fc479 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8caec9e46509ecf86a9db5cd6223e0bb7cedbb24c2a37e2984fdb9618146d957 +size 68943 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset new file mode 100644 index 0000000..ba5f888 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8865430ad2e41d119ae2e672dcbabdbde86ae7700e3593a0b273a6c32d6b3393 +size 68625 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset new file mode 100644 index 0000000..d47e65f --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94e6600ffbb9d23db677edec86c74cbc3a656fdd4202b8ab31e71f939e75a606 +size 68796 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset new file mode 100644 index 0000000..8c16c0f --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae9378547a617f8d289e9d366fbd2de212003b969eba30d63f353e9b177974f7 +size 68782 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset new file mode 100644 index 0000000..1898dfd --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75136218a7a7f8a5dca24059d121aaee146e091065f4912283888789e48022d9 +size 68888 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 68431f1..36d3351 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd3c39d5ee6ba05ce72d2c32d02fdc25af7ffef7f1351aafba6020a1e5a0f289 +oid sha256:ba861bcf0740c9deeacda9c8d8696229550aaa699a89c0ad03ab4dac3ae23f8b size 28811 diff --git a/MEMORY.md b/MEMORY.md index 16d2e96..5795c56 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -612,3 +612,17 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - Lesson: after any blockout regeneration, probe the staged buildings' world X spans and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before trusting a render. A shift of this size is invisible in a screenshot. + +## 2026-11-09 — kit naming + topology validation (blender-modeler checklist) + +- Kit assets now follow the Unreal convention: the Blender object keeps its readable + name (`Kit_Pilaster`), the exported asset and file are `SM_Kit_Pilaster`. The + kit loader strips the `SM_` prefix so the layout code still addresses pieces by + short name. +- `facade_kit_build.py::validate()` runs the blender-modeler cleanup checklist on + every piece before handoff — non-manifold edges, loose vertices, triangle count — + and records the result in the part report. Current kit: 16 pieces, **all + manifold, no loose geometry, 12 tris each**. +- `ue_import_facade_kit.py` now deletes kit assets that the current build no longer + produces, so a rename cannot leave two generations of the same piece in the + folder (the rename from `Kit_*` to `SM_Kit_*` did exactly that until it did). diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index d4d518f..f1d9975 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3e6877437bc5b7034da60d51c01c463c8b0fdc3c267b9be847ce8b59a0ef255 -size 2997213 +oid sha256:1ff0f9b141a0382bbeafcc490d24b4c57b9254066ff92f4bc01eab88231d1e24 +size 2943229 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 7900ebd..cc698e7 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7e7c0fda39926c8cbb27ee36e04192ec32014e3b1711a86e1984870701a017c -size 2079681 +oid sha256:1c515718d963677c6a188525ca81623bfd5669420ea6920e959ef1b49834561d +size 2062307 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 8303781..33950b5 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af2b48830aab4593e00f23d8a04a502b183fbd48a7a3e0c0a3cdeea2c3416683 -size 2883301 +oid sha256:39620fefe8998636484ce1c33e0a517c1a76895cc8473fa539506857b1899450 +size 2807810 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index ebe6b48..8deec71 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebb5422d249d2c332339d9971747e29f067f7a5b4be506a4039495da13adb00a -size 2787860 +oid sha256:dc157c01ec636ad853321547e50a2350eb6a0baaa5ae3d2ecb3d701640381ef1 +size 2745389 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index bcc673d..5e276a4 100644 --- a/Screenshots/review-character.png +++ b/Screenshots/review-character.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:80c0932976ffa2fd3c4ecf60929a0fccb7fd6f5fdfc977cfae2e082eedef3662 -size 2530590 +oid sha256:7622f28548a97c011b79bd17fb2789237e4734a84e21a2c581029b55bc0ceb7b +size 2759479 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index e1daff6..a23dc45 100644 --- a/Screenshots/review-ground-contact.png +++ b/Screenshots/review-ground-contact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4afd2eaeec6a97a34c7649437ce0567324d724d5d04a5da2e0ba73f84eae47d1 -size 1754454 +oid sha256:eee3bdf636a502f17287b2ddd0ff7737c05793c5dd13dd32b75280b3a4c716f9 +size 1823902 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index 4a42281..39a18e3 100644 --- a/Screenshots/review-hero-facade-front.png +++ b/Screenshots/review-hero-facade-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10b7a138b757c3cef7dae03c8ae2c7c1ec87655b69e4b8bdf559622dd574c4b5 -size 2306751 +oid sha256:dc44f5ae5ec115819983f4c447d785126992e69052e28ff65128ce2a640611d8 +size 2489898 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index e520bf6..f10be93 100644 --- a/Screenshots/review-hero-facade-oblique.png +++ b/Screenshots/review-hero-facade-oblique.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7d6027fd1cf8a1c14c74db69153f9d5b978d005cf4d9072dc306cdc06299120 -size 1514483 +oid sha256:895f0878afd3fff9af2aa42ac5d1d8a0937aea2a4c48ddd6f086ef270fc4fd6c +size 1475854 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index e8d89aa..d9a6002 100644 --- a/Screenshots/review-hero-portals.png +++ b/Screenshots/review-hero-portals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ae079e24960fb2cff61efa72188a801640bea932ef9e0d88a3c490b8de4bcaf -size 1323136 +oid sha256:83b2c83c5a1f7f8afbfdfc21d083250b7533f2f1dcec49dcb5c90e117981ab6b +size 1364141 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index 1f7f805..581a127 100644 --- a/Screenshots/review-npc-prefab.png +++ b/Screenshots/review-npc-prefab.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0555e368b4c800c2bc2880af188f1d6f313d0dd54dc99583bdbf756025b78a6 -size 1737659 +oid sha256:f2e6cf03fb638db8c7f12562c546a8100e50d3aa19260da07aec889792df6ce4 +size 1802790 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index d2f4b4a..673ec77 100644 --- a/Screenshots/review-overview.png +++ b/Screenshots/review-overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01a797c5875f6b9cdb6a21d684063fee21f83255f4bc5136df0a110d0862896d -size 2571295 +oid sha256:0b2e77285a958881ef4a2c72b960e122458e45dfd4efbe73d79b8190df9f7221 +size 2395221 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index 2d84235..6ddec72 100644 --- a/Screenshots/review-player-placement.png +++ b/Screenshots/review-player-placement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0ececa978f34d1be3d92924a92a87378eb81d6cfb7a1cca9089154c5f3072c4 -size 2022640 +oid sha256:87cdc11f8b3bb37429d87922834d274afbb8a405bead088bf723249b984fa788 +size 2212624 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 56e4772..69afbf8 100644 --- a/Screenshots/review-scale-cubes.png +++ b/Screenshots/review-scale-cubes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b14730cd7371d8580b385719d5465f71d1108350b85bb03774c2dc2dcf3a69fb -size 1941562 +oid sha256:e1e49774d2bfa0a6240461c2adee32c518af1728a27f20f280014e2248d13683 +size 2144961 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index a089e80..d97501e 100644 --- a/Screenshots/review-shadow-compare.png +++ b/Screenshots/review-shadow-compare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a89c068cab13677e917c003714933cf898df4f86efcb04fa099f1067c484616 -size 1716760 +oid sha256:2b2e9f62cb797028d2cbbb31e49cab6829ffe7a060f525d7b044dcf41d625cfc +size 1844130 diff --git a/Tools/facade_kit_build.py b/Tools/facade_kit_build.py index c60e778..30b8ff0 100644 --- a/Tools/facade_kit_build.py +++ b/Tools/facade_kit_build.py @@ -76,11 +76,39 @@ def make_material(name, value): return mat +def validate(obj, tri_budget=4000): + """Topology check before handoff (blender-modeler cleanup checklist). + + Reports non-manifold edges, loose geometry and the triangle count. Kit pieces + are primitive solids, so any non-manifold edge means a modelling slip rather + than an intentional hole. + """ + import bmesh + + me = obj.data + bm = bmesh.new() + bm.from_mesh(me) + bm.edges.ensure_lookup_table() + non_manifold = [e for e in bm.edges if len(e.link_faces) != 2] + loose_verts = [v for v in bm.verts if not v.link_edges] + tri_count = sum(len(f.verts) - 2 for f in bm.faces) + bm.free() + if non_manifold or loose_verts: + print(f"[kit] WARNING {obj.name}: {len(non_manifold)} non-manifold edge(s), " + f"{len(loose_verts)} loose vertex/vertices") + if tri_count > tri_budget: + print(f"[kit] WARNING {obj.name}: {tri_count} tris exceeds the {tri_budget} budget") + return {"manifold": not non_manifold, "loose_verts": len(loose_verts), + "tris": tri_count} + + def note(obj, note_text): PARTS.append({ "object": obj.name, + "asset": "SM_" + obj.name, "dims_m": [round(d, 3) for d in obj.dimensions], "location_m": [round(c, 3) for c in obj.location], + "topology": validate(obj), "evidence": note_text, }) print(f"[kit] {obj.name:22} {obj.dimensions.x:.2f} x {obj.dimensions.y:.2f} " @@ -235,7 +263,9 @@ def export_group(objects, out_dir): bpy.ops.object.select_all(action="DESELECT") obj.select_set(True) bpy.context.view_layer.objects.active = obj - path = os.path.join(out_dir, obj.name + ".fbx") + # Unreal naming convention for static meshes is SM_; the Blender + # object keeps its readable name, the asset the importable one. + path = os.path.join(out_dir, "SM_" + obj.name + ".fbx") fbx_preset.export_static(path, selection=True) exported.append(path) return exported diff --git a/Tools/ue_apply_facade_kit.py b/Tools/ue_apply_facade_kit.py index 04e6ae7..f332d3c 100644 --- a/Tools/ue_apply_facade_kit.py +++ b/Tools/ue_apply_facade_kit.py @@ -53,7 +53,10 @@ def load_kit() -> dict: for data in registry.get_assets(filt): mesh = unreal.EditorAssetLibrary.load_asset(str(data.package_name)) if mesh: - kit[mesh.get_name()] = mesh + # Assets are SM_-prefixed (Unreal convention); the layout code refers to + # pieces by their short name, so key on the name without the prefix. + name = mesh.get_name() + kit[name[3:] if name.startswith("SM_") else name] = mesh return kit diff --git a/Tools/ue_import_facade_kit.py b/Tools/ue_import_facade_kit.py index 4419907..a8742f1 100644 --- a/Tools/ue_import_facade_kit.py +++ b/Tools/ue_import_facade_kit.py @@ -42,6 +42,16 @@ def main() -> None: log(f"no FBX files in {SRC}") return + # Drop kit assets that the current build no longer produces (the pieces were + # renamed to follow the SM_ convention), so the folder cannot accumulate two + # generations of the same piece. + expected = {os.path.splitext(f)[0] for f in files} + for existing in unreal.EditorAssetLibrary.list_assets(DEST, recursive=False): + name = existing.split("/")[-1].split(".")[0] + if name not in expected: + unreal.EditorAssetLibrary.delete_asset(f"{DEST}/{name}") + log(f"removed stale kit asset {name}") + for filename in files: asset_name = os.path.splitext(filename)[0] task = unreal.AssetImportTask() -- 2.47.3 From ad5bb25009f0887c740769118ed31183fcf841d4 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:44:17 +0100 Subject: [PATCH 07/13] qa(0.1.4): verdict document with budgets, evidence and defect severities Per the qa-review skill: explicit verdict, measured budgets, the screenshots that carry the claim, defects by severity and what remains unverified. Verdict SHIP WITH NOTES - geometry/placement/budgets verified by measurement, the visual half explicitly pending human review (the vision pre-check is unreliable on this content and was not used as evidence). Also fixes the last QA finding: kit pieces kept the grey M_Kit_* material the FBX import creates, so glazing was not glass and shopfronts were not stone. Assembly now assigns M_FacadeGlass to glazing, M_FacadeStone to surrounds, M_FacadeStoneShadow to doors and M_FacadeBanner to fascias (6/6 materials resolved), and the importer deletes the unused M_Kit_* materials (Kit folder now exactly 16 SM_ meshes). Packaged QA re-verified after the change: walk, transect 0 holes, wall probe 0/35, facade probe 0/9, RESULT PASS. --- Docs/qa/0.1.4-facade-qa.md | 81 +++++++++++++++++++ Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Hollywood/Kit/M_Kit_cornice.uasset | 3 - .../Content/Hollywood/Kit/M_Kit_door.uasset | 3 - .../Content/Hollywood/Kit/M_Kit_lintel.uasset | 3 - .../Hollywood/Kit/M_Kit_parapet.uasset | 3 - .../Hollywood/Kit/M_Kit_pilaster.uasset | 3 - .../Hollywood/Kit/M_Kit_shopfront.uasset | 3 - .../Content/Hollywood/Kit/M_Kit_sill.uasset | 3 - .../Content/Hollywood/Kit/M_Kit_window.uasset | 3 - .../Hollywood/Kit/SM_Kit_Coping.uasset | 4 +- .../Hollywood/Kit/SM_Kit_Cornice.uasset | 4 +- .../Content/Hollywood/Kit/SM_Kit_Door.uasset | 4 +- .../Hollywood/Kit/SM_Kit_DoorJamb_L.uasset | 4 +- .../Hollywood/Kit/SM_Kit_DoorJamb_R.uasset | 4 +- .../Hollywood/Kit/SM_Kit_Lintel.uasset | 4 +- .../Hollywood/Kit/SM_Kit_Parapet.uasset | 4 +- .../Hollywood/Kit/SM_Kit_Pilaster.uasset | 4 +- .../Hollywood/Kit/SM_Kit_ShopFascia.uasset | 4 +- .../Hollywood/Kit/SM_Kit_ShopGlass.uasset | 4 +- .../Hollywood/Kit/SM_Kit_ShopPier_L.uasset | 4 +- .../Hollywood/Kit/SM_Kit_ShopPier_R.uasset | 4 +- .../Content/Hollywood/Kit/SM_Kit_Sill.uasset | 4 +- .../Hollywood/Kit/SM_Kit_Threshold.uasset | 4 +- .../Hollywood/Kit/SM_Kit_WindowBay.uasset | 4 +- .../Hollywood/Kit/SM_Kit_WindowGlass.uasset | 4 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- Screenshots/review-character.png | 4 +- Screenshots/review-ground-contact.png | 4 +- Screenshots/review-hero-facade-front.png | 4 +- Screenshots/review-hero-facade-oblique.png | 4 +- Screenshots/review-hero-portals.png | 4 +- Screenshots/review-npc-prefab.png | 4 +- Screenshots/review-overview.png | 4 +- Screenshots/review-player-placement.png | 4 +- Screenshots/review-scale-cubes.png | 4 +- Screenshots/review-shadow-compare.png | 4 +- Tools/ue_apply_facade_kit.py | 28 ++++++- Tools/ue_import_facade_kit.py | 6 ++ 43 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 Docs/qa/0.1.4-facade-qa.md delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset delete mode 100644 Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset diff --git a/Docs/qa/0.1.4-facade-qa.md b/Docs/qa/0.1.4-facade-qa.md new file mode 100644 index 0000000..7a7cf0c --- /dev/null +++ b/Docs/qa/0.1.4-facade-qa.md @@ -0,0 +1,81 @@ +# QA review — `0.1.4` façade work + +Checklist gate for the façade module kit, the replacement materials and the corrected blockout frame. Written per the `qa-review` skill: budgets, the screenshots that carry the claim, defects by severity, and an explicit verdict. + +Date: 2026-11-09 · Branch: `facades-0.1.4` · Build under test: packaged `GigLineQA`, Development, UE 5.8.4, Vulkan SM5. + +## Result: SHIP WITH NOTES + +The geometry, placement and budgets are verified by measurement. The **visual** half of this review is **not** complete: nobody has looked at the rendered set since the kit went on, so the reference-match verdict is pending a human. The notes at the end are what that review has to confirm. + +## Budgets + +| Asset group | Count | Triangles | Texture | Notes | +| --- | --: | --: | --- | --- | +| Hero façade | 148 parts, 1 mesh | 3 044 | none (graph materials) | was 1 892 before the Ionic detail | +| Façade kit | 16 meshes | 12 tris each = 192 | none | boxes; complex-as-simple collision | +| Kit instances in `DemoMap` | 625 actors | ~7 500 | shared materials | 8 buildings | +| Frontage buildings | 6 meshes | blockout volumes | 8 × 512² maps | `M_FacadeStone`, `M_Stucco`, … | +| North buildings | 2 meshes | blockout volumes | shared | TCL, Tussaud's | +| Site meshes | 7 | 55 k tris (terrain-dominated) | shared | terrain/road/sidewalks/kerbs/crossing | + +Texture set: `T_StoneAshlar`, `T_Stucco`, `T_Asphalt`, `T_Concrete`, each `_BC` + `_R` at 512², deterministic and regenerable. + +## Evidence + +Behaviour, packaged build (`-qaselftest`): + +``` +walk moved 761 cm in 2 s -> PASS (Z 456 -> 456) +transect 1456 samples, 0 holes -> PASS +wall probe 0/35 blocking hits inside 21 m -> PASS +facade probe 0/9 directions without a blocking wall -> PASS +RESULT walk=1 reset=1 pause=1 unpause=1 -> PASS +``` + +Geometry and placement, probed from the live map: + +- Frontage spans match the verified block: 6922 −24276..−17700, 6902 −17749..−14591, **hero −14644..−12028 between them**, theatre −12082..−8210, 6822 −7854..−7252, 6806 −6608..−3307, 6800 −3320..−1437. North: TCL −22889..−18442, Tussaud's −27071..−22799. +- Kit placed from world bounds (`get_actor_bounds`), e.g. theatre pieces at −12082..−8694, inside the theatre's own span. +- Kit module **4.96 m**, storey **3.10 m**, door **2.45 m**, window **1.60 × 1.85 m**. +- Kit topology: 16 pieces, **all manifold**, no loose vertices, 12 tris each. +- Asset hygiene: `/Game/Hollywood/Kit` holds exactly 16 `SM_` meshes and no leftover auto-imported materials. + +Screenshots: the committed set in [`Screenshots/`](../Screenshots/README.md), all 14 re-rendered for this state — hero shots 8–10 and demo views 11–14 carry the change. + +## Defects + +### Blockers + +None outstanding. Two were found and fixed during this cycle: + +- **Wrong frame (fixed).** The `0.1.2` blockout was regenerated without `--origin`, shifting the whole frontage ≈119 m in X and 2.4 m in Y and dropping the hero out of the 6902/6838 gap. Regenerated with `--origin 34.101545,-118.338718` (affine residual 0.0 cm); spans re-verified against [`Geo/verification/2026-11-06-el-capitan-block.md`](../Geo/verification/2026-11-06-el-capitan-block.md). +- **Kit placed 12 km away (fixed).** `ue_apply_facade_kit` used the static-mesh bounding box, which is building-line-local. Now uses `get_actor_bounds()` (world space). + +### Majors + +- **Window bays were inside the solid wall (fixed).** The first piece modelled the whole bay at +Y, so every window sat inside the blockout volume and was invisible. The surround now projects 3 cm with glazing 6 cm behind its face. +- **Kit had no materials (fixed).** Pieces kept the grey `M_Kit_*` material the FBX import creates, so the glazing was not glass and the shopfronts were not stone. Assembly now assigns `M_FacadeGlass` to glazing, `M_FacadeStone` to surrounds, `M_FacadeStoneShadow` to doors and `M_FacadeBanner` to fascias. + +### Minors + +- Kit pieces are boxes: no reveals, sills or mouldings beyond the kit's own bands. Acceptable at this patch's fidelity; the kit is the vocabulary, not the detail. +- `M_Kit_*` materials are deleted at import rather than reused, so the kit's Blender materials carry no shading intent — the Unreal set is authoritative. Documented, not silent. +- Actor labels for kit pieces are unique per building but long (`Kit_elcapitantheatre_window_3_2`). + +### Notes (polish, not required for `v0.1.4`) + +- Hero fluting, and the motto band/medallions as geometry, are still pending. +- The El Capitan Theatre has no marquee or blade sign yet — the features that make it read as itself. +- Cornice dentil spacing, abacus and plinth dimensions remain recorded assumptions. + +## Not verified here + +- **Reference match.** Needs a human comparing `Screenshots/demo/*` against `Art/reference/hollywood/`. The vision pre-check is demonstrably unreliable on this content (it rejected shots the owner has since approved), so it was not used as evidence. +- **Silhouette and shading at street level** — the same review covers it. + +## Next actions + +1. Owner review of the 14 demo/review shots; record approval in `Screenshots/README.md`. +2. Then the remaining `0.1.4` modelling: theatre marquee + blade sign, hero fluting/motto band, blockout cleaning pass. +3. `CHANGELOG.md` entry and `v0.1.4` tag once (1) is signed off. diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index c0b0255..499e509 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:fbbfbd1af034947cf225a95f7c093c80fc415c2520a3bc4be2d8e95e6925170a -size 921274 +oid sha256:6947def46f970b8fcd21a77312c5d9c683723d6c2443e742a41c45769cc7c009 +size 949399 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset deleted file mode 100644 index 817d539..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_cornice.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02921296aa4e2107411a29db7b2d7b07d46dc0b7b3be6fa74ffb4d6123e3e931 -size 56615 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset deleted file mode 100644 index f0d12b4..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_door.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed76fe8a3e3fd3f2060feef885f20b4b7b4f26cc9c36c742b77957e0b8427c83 -size 56558 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset deleted file mode 100644 index c2effdd..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_lintel.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e82df3759ae16c1ace533458f0e18dba4097e92049bfdcd2e2022ad5f1158f0c -size 56596 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset deleted file mode 100644 index d5f0bd5..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_parapet.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d1cedd8b5e9faf255d1aa6194f45809d0599d13715cbaf21fdfbacc0a08758b7 -size 56604 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset deleted file mode 100644 index de12c7e..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_pilaster.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8db53e9022174775a2bd23cb08538f6ed2a3ef376c0f3ede3028580bb3e1b57b -size 56634 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset deleted file mode 100644 index 1c88810..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_shopfront.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2acb6664dce7be4e009fc986a0c35d7723c0063b8de80e0d1270ea4c33da1553 -size 56656 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset deleted file mode 100644 index 36eaf05..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_sill.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b347bb058b46f29d49d75388988faa08a6484663d384b7599260174105be46a -size 56569 diff --git a/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset b/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset deleted file mode 100644 index 522110f..0000000 --- a/Game/GigLine/Content/Hollywood/Kit/M_Kit_window.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a810125b00d7ac8fb3186c7d0c4e6da0579ce05de267c8800514ef2840c8f31 -size 56616 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset index 99f2aab..3f4f2b2 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Coping.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24a967d6e43238db6972d0488e0680e5b529f0ecc13b0fe94d7c460173073fe9 -size 68736 +oid sha256:9715cd044fb50b721cfb34a9b89e144ed63d4afd45047deb6cc8d04fea0b61ea +size 72195 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset index ea90c73..fc0d09d 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Cornice.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7aea26e6c397c7cdb68392ea7d314c517e94712361095b738fdcf7f0db25757 -size 68745 +oid sha256:61ca4e7c9da1c3d75e09bfc12d72dcea4e014d19c80acb75a0d550ca46936564 +size 72206 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset index 610ec2e..1773f02 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Door.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd344a450dc99e9e21808967b33b2a785da75f90c4a0b29d6458612013a2bb6a -size 68621 +oid sha256:047ebceefc66501c7b37f279edabc751308dae32af3a6724ff3aa1e3295ed930 +size 72097 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset index 460b603..4591cc1 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_L.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c581fb39f3017d38b0a84451049242d7f465750afa24d5986c2e98717d15598 -size 68806 +oid sha256:25b2e6cef34d45635abe577d8c5556f520797d6d2a2dd557322343e9427c47ec +size 72223 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset index 2a16d39..24b14a0 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_DoorJamb_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6637ffd140dd2ba4c35d971df289c40e371ecfcad7c89fe7f98be37eba5a9192 -size 68815 +oid sha256:8d541bff40babb0fe6ffafd7c8ed287f687aec7b8c077a831b4ff51864a2e9f9 +size 72232 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset index 8d0e368..da5eb25 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Lintel.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cefb4896f71f931e75b22f772ef02714d38001f38118eaf6fcab75e842e03664 -size 68699 +oid sha256:56aa941fb6f73bad72990525d9e295ad35e7f5bcceb13bf57e1fa29102ad4a4a +size 72167 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset index 45a4a55..ff5a6f8 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Parapet.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab0c6b4c67c740ea4d0d9bf767bf30d35b0056a96b8911baaa24c39fbceb6a32 -size 68803 +oid sha256:4b6cc365209fd95cb5e14efb3bfbd0f0f7066859366c902e82b19429dd03f7be +size 72195 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset index d34df84..dd49efb 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Pilaster.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6242668e209a5801a93a2482c291d2417320545853d6aa363797f5b8fa74db27 -size 68785 +oid sha256:820b4602361c56c7b827366ae97dc9bc5cfaf75cf741fb6f82549bf32349c1c8 +size 72243 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset index 838bf32..4784b91 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d30784e60c2c14e1c6df5bb3640564309cac13e9301d498a49d98080740625dd -size 68867 +oid sha256:8a091d1c252f4b043c8f942836b37e59bf4ee6071da8e2650be3c5a9b3b4bf13 +size 72322 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset index e3a0de7..f884da3 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopGlass.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d630d6685a991b529943fa90ddf2da1e1219749351ea27d2d23b2d091b685af3 -size 68921 +oid sha256:079087512346f82c9e5c2d6d0c715ef09903e70aec945520b76ffdb79b19b82b +size 72303 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset index d418592..ea3f552 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_L.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5dde20d12e36a3b7f4b5ec106e9a33ec41ad46085e3af37f1311d9f0b89f711 -size 68935 +oid sha256:5b5fc921a674df50b764df1a1b8e5f8d45ee361381fd927f280316e99b3d139e +size 72317 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset index e1fc479..2f8d84a 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_ShopPier_R.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8caec9e46509ecf86a9db5cd6223e0bb7cedbb24c2a37e2984fdb9618146d957 -size 68943 +oid sha256:3e0b4591b422089581eea7c58bf52cee4f513bbabdc787c1da5fc4e9b4b71d8b +size 72325 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset index ba5f888..aba93da 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Sill.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8865430ad2e41d119ae2e672dcbabdbde86ae7700e3593a0b273a6c32d6b3393 -size 68625 +oid sha256:a27b1e21404322d5647a64218560e41ea89fc359bb410944484b9e637964faf1 +size 72093 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset index d47e65f..e7adfbc 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_Threshold.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94e6600ffbb9d23db677edec86c74cbc3a656fdd4202b8ab31e71f939e75a606 -size 68796 +oid sha256:cdc210539a17a7cacd8f3968187f0504989a9d5c2485bd9d1220a45e8e3e219a +size 72203 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset index 8c16c0f..05dbbb8 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowBay.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae9378547a617f8d289e9d366fbd2de212003b969eba30d63f353e9b177974f7 -size 68782 +oid sha256:30bab2b7807153d2a534888cbe5fb7b0e71dd329623e7d6212fa2ab26d6f5028 +size 72254 diff --git a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset index 1898dfd..18d562e 100644 --- a/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset +++ b/Game/GigLine/Content/Hollywood/Kit/SM_Kit_WindowGlass.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75136218a7a7f8a5dca24059d121aaee146e091065f4912283888789e48022d9 -size 68888 +oid sha256:82c63da22750af7b3181160f4a4c7909c81065ffa117341a59f3ff378b73e39c +size 72293 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 36d3351..cb2a9be 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba861bcf0740c9deeacda9c8d8696229550aaa699a89c0ad03ab4dac3ae23f8b +oid sha256:a6f170a72a1c3c220b9e52a1c2099d6bcd3dc96dc640038cef039b2c41f5fdaf size 28811 diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index f1d9975..4d9596d 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ff0f9b141a0382bbeafcc490d24b4c57b9254066ff92f4bc01eab88231d1e24 -size 2943229 +oid sha256:f105779e16a28cfe166df9efd3c98c38f71875380ab1fae1ff2e160ff510f1e1 +size 3078975 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index cc698e7..5c7a939 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c515718d963677c6a188525ca81623bfd5669420ea6920e959ef1b49834561d -size 2062307 +oid sha256:05861cdcb4e67da8e2fc257e95c3345fb4f6ff37fcf37bc2fc6d454822cc82cf +size 2443779 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 33950b5..99b4ab2 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39620fefe8998636484ce1c33e0a517c1a76895cc8473fa539506857b1899450 -size 2807810 +oid sha256:2788f8c6e57fa0bc2076fd9febf1ef632ebfbe5bceae9aa0b8d71b49ef705914 +size 2914546 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 8deec71..516869b 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc157c01ec636ad853321547e50a2350eb6a0baaa5ae3d2ecb3d701640381ef1 -size 2745389 +oid sha256:5ebcc70ac33b142657ba96a6165559be98a4c6e66b5fdb414f9e9f60115f22c6 +size 2864294 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png index 5e276a4..575f9ef 100644 --- a/Screenshots/review-character.png +++ b/Screenshots/review-character.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7622f28548a97c011b79bd17fb2789237e4734a84e21a2c581029b55bc0ceb7b -size 2759479 +oid sha256:cb9307133d8f02dcc4da2f4e3b91588f57244f6d5cbb7ab6f2e755dc65021f20 +size 2538731 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png index a23dc45..c822d56 100644 --- a/Screenshots/review-ground-contact.png +++ b/Screenshots/review-ground-contact.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eee3bdf636a502f17287b2ddd0ff7737c05793c5dd13dd32b75280b3a4c716f9 -size 1823902 +oid sha256:2bfbe4847a98fdbc42050406d4b26569bf910f5376994eeea3d646e71e0f36d5 +size 1748441 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png index 39a18e3..715236c 100644 --- a/Screenshots/review-hero-facade-front.png +++ b/Screenshots/review-hero-facade-front.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc44f5ae5ec115819983f4c447d785126992e69052e28ff65128ce2a640611d8 -size 2489898 +oid sha256:b1dfe09f9de990c18fab1f9289119f6a98566523168b746c408d2da1aca8203c +size 2283522 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png index f10be93..5a4f65e 100644 --- a/Screenshots/review-hero-facade-oblique.png +++ b/Screenshots/review-hero-facade-oblique.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:895f0878afd3fff9af2aa42ac5d1d8a0937aea2a4c48ddd6f086ef270fc4fd6c -size 1475854 +oid sha256:ee46da9476f90434c8a676d0d4726f9bc4543d72d22b5db44493b44072ed8729 +size 1518947 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png index d9a6002..ff90639 100644 --- a/Screenshots/review-hero-portals.png +++ b/Screenshots/review-hero-portals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83b2c83c5a1f7f8afbfdfc21d083250b7533f2f1dcec49dcb5c90e117981ab6b -size 1364141 +oid sha256:cd2350c9bf572a05cd7323368a3f435b7dea5b1daaa530f9512bfea1314c79f1 +size 1328667 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png index 581a127..239857d 100644 --- a/Screenshots/review-npc-prefab.png +++ b/Screenshots/review-npc-prefab.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2e6cf03fb638db8c7f12562c546a8100e50d3aa19260da07aec889792df6ce4 -size 1802790 +oid sha256:555db441a3a709057db8f61b367772d5b0b8148391be3299a60f6c13bd05ac20 +size 1736426 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png index 673ec77..936cdc2 100644 --- a/Screenshots/review-overview.png +++ b/Screenshots/review-overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b2e77285a958881ef4a2c72b960e122458e45dfd4efbe73d79b8190df9f7221 -size 2395221 +oid sha256:42eeaadb8795d84af81931b13fe4644601e8aadf33539b02d171e86730dc8269 +size 2622214 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png index 6ddec72..887dc65 100644 --- a/Screenshots/review-player-placement.png +++ b/Screenshots/review-player-placement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87cdc11f8b3bb37429d87922834d274afbb8a405bead088bf723249b984fa788 -size 2212624 +oid sha256:ffb788897558080e8100cdf174b0998f6b69ea4ffe0bf3d25a79d94f6aafb3d8 +size 2026302 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png index 69afbf8..7a713c0 100644 --- a/Screenshots/review-scale-cubes.png +++ b/Screenshots/review-scale-cubes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1e49774d2bfa0a6240461c2adee32c518af1728a27f20f280014e2248d13683 -size 2144961 +oid sha256:bae1665389a83d7c7b7339ba626dd1eb5362322f3c4c05c93c5808aaa1fd4014 +size 1934977 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png index d97501e..e0a75c2 100644 --- a/Screenshots/review-shadow-compare.png +++ b/Screenshots/review-shadow-compare.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b2e9f62cb797028d2cbbb31e49cab6829ffe7a060f525d7b044dcf41d625cfc -size 1844130 +oid sha256:23457d81639ea5dcdb4fdea9af0a424a968f25b3559c30da5ede45355c6eac66 +size 1722925 diff --git a/Tools/ue_apply_facade_kit.py b/Tools/ue_apply_facade_kit.py index f332d3c..5606b91 100644 --- a/Tools/ue_apply_facade_kit.py +++ b/Tools/ue_apply_facade_kit.py @@ -31,6 +31,18 @@ import ue_common # noqa: E402 KIT = "/Game/Hollywood/Kit" RESULT = "/tmp/gigline/kit-apply.txt" +# Material per kit piece. Without this the pieces keep the grey material the FBX +# import creates (M_Kit_*), so the glazing is not glass and the shopfronts are not +# stone - the kit reads as a plastic scaffold over the buildings. +KIT_MATERIALS = { + "Kit_WindowGlass": "M_FacadeGlass", + "Kit_ShopGlass": "M_FacadeGlass", + "Kit_Door": "M_FacadeStoneShadow", + "Kit_WindowBay": "M_FacadeStone", + "Kit_ShopFascia": "M_FacadeBanner", +} +KIT_MATERIAL_FALLBACK = "M_FacadeStone" + BAY_CM = 496.0 # the kit module STOREY_CM = 310.0 SHOP_FLOOR_CM = 310.0 # ground floor is one storey tall @@ -119,7 +131,10 @@ def apply_to_actor(actor, kit, materials=None, prefix=None): storeys = max(1, int((height - SHOP_FLOOR_CM) // STOREY_CM)) def kit_material(name): - return (materials or {}).get(name) + if materials is None: + return None + want = KIT_MATERIALS.get(name, KIT_MATERIAL_FALLBACK) + return materials.get(want) placed = 0 for bay in range(bays): @@ -177,6 +192,15 @@ def main() -> None: return log(f"kit meshes: {len(kit)}") + # Site materials built by Tools/ue_build_demo_map.py. + materials = {} + for name in ("M_FacadeStone", "M_FacadeStoneShadow", "M_FacadeGlass", + "M_FacadeBanner", "M_Stucco", "M_Concrete"): + asset = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}") + if asset: + materials[name] = asset + log(f"kit materials resolved: {len(materials)}/6") + level = "/Game/Hollywood/DemoMap" if not unreal.EditorLevelLibrary.load_level(level): log(f"could not load {level}") @@ -198,7 +222,7 @@ def main() -> None: log(f"frontage actors: {len(targets)}") total = 0 for actor in targets: - total += apply_to_actor(actor, kit) + total += apply_to_actor(actor, kit, materials) log(f"placed {total} kit pieces") saved = unreal.EditorLoadingAndSavingUtils.save_map( diff --git a/Tools/ue_import_facade_kit.py b/Tools/ue_import_facade_kit.py index a8742f1..a3d50e1 100644 --- a/Tools/ue_import_facade_kit.py +++ b/Tools/ue_import_facade_kit.py @@ -83,6 +83,12 @@ def main() -> None: if mesh is None: log(f"{asset_name}: IMPORT FAILED") continue + # The FBX import also creates an M_Kit_* material per piece. The demo + # assigns real site materials at assembly time, so those are clutter. + for slot in mesh.get_editor_property("static_materials"): + mat = slot.get_editor_property("material_interface") + if mat and mat.get_name().startswith("M_Kit_"): + unreal.EditorAssetLibrary.delete_asset(mat.get_path_name().split(".")[0]) unreal.GigLineAssetTools.set_complex_collision_as_simple(mesh, True) unreal.EditorAssetLibrary.save_loaded_asset(mesh) box = mesh.get_bounding_box() -- 2.47.3 From bd1f8a9c2ea91a4e851b2604d5c9e7d8bde10ae7 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 19:52:25 +0100 Subject: [PATCH 08/13] feat(qa): reference-vs-render iteration loop; screenshots are demo-only Tools/visual_loop.py: - --verify gates every capture against its expectation in Tools/visual_targets.json (missing, blank, tone-less or wrong-size shots fail), so 'each screenshot shows what was expected' is checked rather than assumed. - --compare measures luminance, RMS contrast, edge density, palette distance and a 3x3 luma grid against the reference photograph, with a 0-1 similarity score. - --iteration N writes Art/reference/iterations/N/{metrics.json,feedback.md}; the feedback names the skill to consult (blender-modeler, archviz, materials, lookdev, camera-cinematography), so the next modelling pass starts from a measurement. - Iterations are gitignored until the owner confirms a shot; only confirmed shots are promoted into Screenshots/. Screenshots/ holds demo-map captures only now: render_screenshots.sh renders just DemoMap, collects the shots and runs the verification gate (--verify); the review staging map is no longer rendered for review and its PNGs are removed. The README is rewritten around the four demo shots with per-shot expected content and a verified column, and no longer claims the review staging set. Tests: 67 pass (13 new) covering metrics on synthetic images, comparison scoring, the feedback rules and the target spec (including that every target is a demo shot and that iterations are gitignored). --- .gitignore | 4 + Game/GigLine/Content/Hollywood/DemoMap.umap | 2 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 24 ++ Screenshots/README.md | 78 ++-- Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- Screenshots/review-character.png | 3 - Screenshots/review-ground-contact.png | 3 - Screenshots/review-hero-facade-front.png | 3 - Screenshots/review-hero-facade-oblique.png | 3 - Screenshots/review-hero-portals.png | 3 - Screenshots/review-npc-prefab.png | 3 - Screenshots/review-overview.png | 3 - Screenshots/review-player-placement.png | 3 - Screenshots/review-scale-cubes.png | 3 - Screenshots/review-shadow-compare.png | 3 - Tools/README.md | 4 +- Tools/render_screenshots.sh | 66 ++- Tools/tests/test_visual_loop.py | 139 +++++++ Tools/visual_loop.py | 389 ++++++++++++++++++ Tools/visual_targets.json | 53 +++ requirements-tools.txt | 2 +- 25 files changed, 688 insertions(+), 121 deletions(-) delete mode 100644 Screenshots/review-character.png delete mode 100644 Screenshots/review-ground-contact.png delete mode 100644 Screenshots/review-hero-facade-front.png delete mode 100644 Screenshots/review-hero-facade-oblique.png delete mode 100644 Screenshots/review-hero-portals.png delete mode 100644 Screenshots/review-npc-prefab.png delete mode 100644 Screenshots/review-overview.png delete mode 100644 Screenshots/review-player-placement.png delete mode 100644 Screenshots/review-scale-cubes.png delete mode 100644 Screenshots/review-shadow-compare.png create mode 100644 Tools/tests/test_visual_loop.py create mode 100644 Tools/visual_loop.py create mode 100644 Tools/visual_targets.json diff --git a/.gitignore b/.gitignore index fc01586..f8ea7bf 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,10 @@ Thumbs.db /Geo/raw/mapbox-cache/ /Geo/verification/*mapbox* +# Visual-loop iterations: generated comparisons stay uncommitted until the owner +# confirms a shot. Only Screenshots/ is committed. +/Art/reference/iterations/ + # Local, private reference material — never committed (see Docs/reference-notes-hollywood.md) /Art/reference/ /Docs/data-inventory.md diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 499e509..aa39faa 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:6947def46f970b8fcd21a77312c5d9c683723d6c2443e742a41c45769cc7c009 +oid sha256:78027bd7ca19b89d4c8026c8429d6dde5a220427c2627de2c50a19e7686b9e03 size 949399 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index cb2a9be..8babc6b 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6f170a72a1c3c220b9e52a1c2099d6bcd3dc96dc640038cef039b2c41f5fdaf +oid sha256:f66abdc90461601dfb4fe45f6afd16d7cf80965beaecf06c3377c6b2c75ed5e7 size 28811 diff --git a/MEMORY.md b/MEMORY.md index 5795c56..cca7e7e 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -626,3 +626,27 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - `ue_import_facade_kit.py` now deletes kit assets that the current build no longer produces, so a rename cannot leave two generations of the same piece in the folder (the rename from `Kit_*` to `SM_Kit_*` did exactly that until it did). + +## 2026-11-09 — visual comparison loop; screenshots are demo-only + +- `Tools/visual_loop.py` closes the reference → render → feedback loop. + `--verify` gates captures against `Tools/visual_targets.json` (blank, tone-less, + wrong-size fail); `--compare` measures luma, RMS contrast, edge density, palette + distance and a 3x3 luma grid against the reference and prints a 0–1 score; + `--iteration N` writes `Art/reference/iterations/N/{metrics.json,feedback.md}`. + The feedback names the skill to consult, so the next modelling pass starts from a + measurement rather than an opinion. +- **Iterations are gitignored** (`/Art/reference/iterations/`); only confirmed shots + are promoted into `Screenshots/`. A test asserts the ignore rule and that every + target is a demo-map shot. +- `Screenshots/` now holds **demo-map captures only** — the review staging map is no + longer rendered for review. `render_screenshots.sh --verify` renders the demo map, + collects the shots and runs the verification gate in one step. +- **PIL gotchas:** `FIND_EDGES` leaves its own border ring at 128 (236/3600 px on a + 60x60 frame = 6.5% "detail" on a perfectly flat image), so crop **after** + filtering — cropping before does not help, the new border gets the same + treatment. Palette shares must divide by the full-image area, not the cropped one. +- First comparison (iteration 2): spawn view 0.70, street level 0.35, crossing 0.41, + north buildings 0.34. Edge density is the dominant gap everywhere (0.04–0.11 + against 0.20–0.32 in the references) — the render still lacks the relief the + photographs carry, which is exactly what the `0.1.4` façade work is for. diff --git a/Screenshots/README.md b/Screenshots/README.md index 04c3f64..9669d96 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -1,63 +1,51 @@ -# Screenshots — visual review +# Screenshots — demo-map visual review -Rendered in-game captures of the current project content, generated by [`Tools/render_screenshots.sh`](../Tools/render_screenshots.sh). The PNGs are committed alongside this README so a reviewer sees exactly what the build rendered. +Rendered captures of **the demo map only** — what a player sees when the demo runs. Generated by [`Tools/render_screenshots.sh`](../Tools/render_screenshots.sh); the PNGs are committed so a reviewer sees exactly what the build rendered. + +The review staging map (`/Game/Test/ReviewMap`) is not part of this set: it exists to hold the camera list and the hero detail rig, and it is not what ships. ## How to regenerate ```bash -Tools/render_screenshots.sh +Tools/render_screenshots.sh --verify ``` -Rebuilds `Game/GigLine/Content/Test/ReviewMap.umap` from current content, runs the game on it (and on the DemoMap for the walkable views), walks the fixed camera list via `AGigLineShotActor`, captures each shot, copies the PNGs here and exits — about a minute. The camera list lives in `Tools/ue_build_review_map.py`; add new shots there, remove retired ones here. +Stages the demo shot cameras, runs the game on `/Game/Hollywood/DemoMap`, copies the PNGs into `Screenshots/demo/` and then runs [`Tools/visual_loop.py`](../Tools/visual_loop.py) `--verify` to check that each shot still shows the content its expectation describes. About a minute. -> Naming: shots use their stable camera name (no index prefix), so adding or reordering cameras never renumbers files or orphans the old ones. Click an image for the full-size PNG. +## The shot list -## Shot list +Each shot has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) — the content the capture must show — and a verification state produced by `--verify`: it fails if the frame is blank, the wrong size, or has no tonal range. -**Status: whole set ☑ approved 2026-11-09** (owner review of PR #27). Every shot was re-rendered from current `main` content after the building-height calibration was adopted (`v0.1.2`): Mapbox-derived **3.1 m per level** (`Geo/tools/block_height_report.py`), explicit overrides for three rear outbuildings, and the regenerated blockout and frontage that follow — 6922 is now 39.1 m (was 42.1 m) and the El Capitan Theatre 20.5 m (was 20.0 m). +| # | File | View | Expected content | Verified | +| --- | --- | --- | --- | --- | +| 1 | `demo/review-demo-spawn-view.png` | north sidewalk | The whole south block: the neoclassical hero façade between its neighbours, both sidewalks and the road in the foreground | ☑ 2026-11-09 | +| 2 | `demo/review-demo-street-level.png` | eye height, north side | Street level: the hero colonnade with the adjacent frontage buildings either side, shopfronts at pavement level | ☑ 2026-11-09 | +| 3 | `demo/review-demo-crossing.png` | along the crosswalk | The designated mid-block crossing (s=130 m, UE X −14956): zebra bars on the asphalt, south frontage and colonnade behind | ☑ 2026-11-09 | +| 4 | `demo/review-demo-north-buildings.png` | across the Boulevard | The two north-side buildings (TCL Chinese Theatre, Madame Tussaud's) from the south side, full height in frame | ☑ 2026-11-09 | -| Round | What changed | -| --- | --- | -| 2026-11-07 | First approved set; block frontage pass corrected hero and north-building placement | -| 2026-11-08 | Re-rendered after `0.1.1` street geometry (sidewalk upstand flush at the building edge, crossing ramps graded ≤ 8 %); `demo-north-buildings` camera re-pointed (was 9.5 km west, 85 % sky) | -| 2026-11-09 | Re-rendered after the `0.1.2` building-height calibration (3.1 m per level). 12 of 14 PNGs changed; `review-demo-spawn-view` and `review-demo-street-level` are byte-identical to the `0.1.1` round, so the height change reads mostly on the frontage silhouettes | +Approval is per shot, in the table above. Add a row when a shot is added; remove the file in the same change when a shot is retired. -| # | File | Preview | Map | View | What it shows | ☑ | -| --- | --- | --- | --- | --- | --- | --- | -| 1 | `review-overview.png` | [overview](review-overview.png) | Review | 7 m up, NE | Whole review layout: character + both cubes on the floor | ☑ 2026-11-09 | -| 2 | `review-character.png` | [character](review-character.png) | Review | eye height, south | MPFB mannequin, idle pose + warm skin; feet meet the plane | ☑ 2026-11-09 | -| 3 | `review-scale-cubes.png` | [scale cubes](review-scale-cubes.png) | Review | south | Floating vs grounded cube: cast vs contact shadow | ☑ 2026-11-09 | -| 4 | `review-player-placement.png` | [player placement](review-player-placement.png) | Review | spawn | The `AGigLineCharacter` pawn, capsule height and landing | ☑ 2026-11-09 | -| 5 | `review-npc-prefab.png` | [npc prefab](review-npc-prefab.png) | Review | mid, west | The `AGigLineNPCPerson` prefab: collision capsule + visible MPFB body, feet on Z 0 | ☑ 2026-11-09 | -| 6 | `review-ground-contact.png` | [ground contact](review-ground-contact.png) | Review | knee height, feet | Floating/sinking character is obvious here | ☑ 2026-11-09 | -| 7 | `review-shadow-compare.png` | [shadow compare](review-shadow-compare.png) | Review | low angle | Detached (floating) vs touching (grounded) shadows | ☑ 2026-11-09 | -| 8 | `review-hero-portals.png` | [hero portals](review-hero-portals.png) | Review | 8 m, eye height | Three genuine 1.2 m portal recesses with flights and plinth | ☑ 2026-11-09 | -| 9 | `review-hero-facade-front.png` | [hero facade front](review-hero-facade-front.png) | Review | road side, straight-on | Hero façade elevation and column rhythm | ☑ 2026-11-09 | -| 10 | `review-hero-facade-oblique.png` | [hero facade oblique](review-hero-facade-oblique.png) | Review | north-west | Body depth and column line standing proud of the mass | ☑ 2026-11-09 | -| 11 | `demo/review-demo-spawn-view.png` | [spawn view](demo/review-demo-spawn-view.png) | Demo | north sidewalk | The whole block: hero façade between its neighbours, both sidewalks and the road | ☑ 2026-11-09 | -| 12 | `demo/review-demo-street-level.png` | [street level](demo/review-demo-street-level.png) | Demo | eye height, north side | The hero colonnade with the adjacent frontage buildings either side | ☑ 2026-11-09 | -| 13 | `demo/review-demo-crossing.png` | [crossing](demo/review-demo-crossing.png) | Demo | along the crosswalk | The designated mid-block crossing at s=130 m (UE X -14956): three zebra bars on the asphalt, with the south frontage and the hero colonnade behind | ☑ 2026-11-09 | -| 14 | `demo/review-demo-north-buildings.png` | [north buildings](demo/review-demo-north-buildings.png) | Demo | across the Blvd | North-side buildings (TCL, Tussaud's) seen from the south side across the road. **2026-11-08:** camera re-pointed to the buildings' mass centre (was 9.5 km west → 85% sky); now frames both north buildings. Flat surfaces = `0.1.4` façade scope. | ☑ 2026-11-09 | +## The iteration loop (uncommitted) + +`Tools/visual_loop.py` measures each capture against its reference photograph and turns the deltas into modelling actions for the Blender skill packs: + +```bash +python3 Tools/visual_loop.py --compare --iteration 3 # writes Art/reference/iterations/3/ +``` + +That directory is **gitignored**: iterations stay uncommitted until a shot is confirmed, and only confirmed shots are promoted into `Screenshots/demo/`. The feedback file names the affected skill (`blender-modeler`, `archviz`, `materials`, `lookdev`, `camera-cinematography`) so the next modelling pass has a starting point rather than an opinion. + +Note the comparison is only meaningful when the shot and the reference frame the same subject — a wide block view measured against a tight façade crop will always report low edge similarity. ## Scene conventions -- **Review map** (`/Game/Test/ReviewMap`): 160 m floor, top face at Z 0; the character measures Z 0..83 cm. One warm key light plus sky fill. -- **Cubes**: `Cube_ScaleTest_A` rests on the floor, `Cube_ScaleTest_B` floats at Z 2 m; both are exactly 1 m — the scale reference. -- **Demo map** (`/Game/Hollywood/DemoMap`): fixed daytime — sun at pitch −50 / yaw 120 (intensity 4, warm), sky light 2.6, sky atmosphere, height fog, fixed exposure. -- **Materials**: every exported surface carries a look-developed material built in `Tools/ue_build_demo_map.py` (`ensure_material`) because Blender's flat diffuse colours do not survive the FBX → Unreal import. Each has an explicit roughness and a subtle world-space noise blend between two shades: `M_Asphalt` (0.92), `M_Concrete` (0.88), `M_KerbConcrete` (0.85), `M_Ground` (0.98), `M_RoadPaint` (0.62), `M_FacadeStone` (0.72) and `M_Stucco` (0.86) for the north-side buildings. -- **Lighting is dynamic-only** (`r.AllowStaticLighting=False`, all lights Movable), so no lighting build is ever required. - -## What to look for - -- **Ground contact**: feet meet the plane with no gap and no shadow seam. -- **Scale**: everything checks against the 1 m cubes. -- **Façade rhythm**: six columns at 4.96 m across 25.75 m should read as the photographs in silhouette. -- **Frontage heights (new in this round)**: 6922 and the El Capitan Theatre were re-cut to the 3.1 m storey height. Check they still read as the taller west end and the lower cinema respectively against `Art/reference/hollywood/el-capitan-theatre-blade-sign-day-wikimedia-2008-10.jpg`, and that the ordering of the six frontage buildings is unchanged. +- **Demo map** (`/Game/Hollywood/DemoMap`): fixed daytime — sun pitch −62 / yaw 120 (intensity 4, warm), sky light 2.6, sky atmosphere, height fog, fixed exposure. +- **Cameras**: elevation shots sit at their target height (pitch 0, so verticals stay parallel) and use a 42–55° lens to fill the frame from across the street. +- **Materials**: every surface carries a look-developed material from `Tools/ue_build_demo_map.py` — noise-blended base colour, masonry coursing, and a generated texture set (`Tools/generate_facade_textures.py`) sampled world-aligned. Explicit roughness and specular per surface type. ## Known cosmetic issues -- Floor speckle from the `Constant3Vector` wiring in `Tools/ue_build_review_map.py`. -- Hero façade renders pale — Blender flat colours with no normal/roughness, so it reads as untextured stone (the `0.1.4` façade patch, not a lighting bug). -- Banners sit on the column line rather than the wall (acceptable: they hang on the columns in the photographs). -- ~~Banners and window panels float detached from the colonnade wall~~ — **window panes fixed 2026-11-09**: they sat at Y −1.98, 1.44 m in _front_ of the wall and ahead of the column line; they now sit on the wall plane at Y −0.56, behind the columns, and read as dark openings (see `review-hero-facade-front`). -- Between kerb and building line, the walkable surface is the terrain mesh, not a modelled pavement. +- Hero façade renders pale: the stone material is a graph and a texture set, not a scanned surface (the `0.1.4` façade pass is in progress). +- Banners sit on the column line rather than flush to the wall (they hang on the columns in the photographs, so this is acceptable). +- Between kerb and building line the walkable surface is the terrain mesh, not a modelled pavement. +- The El Capitan Theatre has no marquee or blade sign yet — the feature that makes it read as itself. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 4d9596d..91ed79e 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f105779e16a28cfe166df9efd3c98c38f71875380ab1fae1ff2e160ff510f1e1 -size 3078975 +oid sha256:65530cfd12bc783e9a40af40b91926abf09156d27e0ae57f02b625eec8fc96bc +size 3078175 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 5c7a939..c31577c 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05861cdcb4e67da8e2fc257e95c3345fb4f6ff37fcf37bc2fc6d454822cc82cf -size 2443779 +oid sha256:a16d37433312a7e5ee2fecbaba5bee822566a58a1a7c45e6bb7c05c3b7c76689 +size 2443382 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 99b4ab2..900adca 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2788f8c6e57fa0bc2076fd9febf1ef632ebfbe5bceae9aa0b8d71b49ef705914 -size 2914546 +oid sha256:c0145fc8e60aff6d7981c74f950f06ccb31fac4b85fdc80f58ec9aaa50397d8d +size 2942005 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 516869b..d4a549d 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ebcc70ac33b142657ba96a6165559be98a4c6e66b5fdb414f9e9f60115f22c6 -size 2864294 +oid sha256:b7c2afd07d9d24aa196a8151f66452f532e10a8f52bc9f232d64a61c783a6189 +size 2863148 diff --git a/Screenshots/review-character.png b/Screenshots/review-character.png deleted file mode 100644 index 575f9ef..0000000 --- a/Screenshots/review-character.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb9307133d8f02dcc4da2f4e3b91588f57244f6d5cbb7ab6f2e755dc65021f20 -size 2538731 diff --git a/Screenshots/review-ground-contact.png b/Screenshots/review-ground-contact.png deleted file mode 100644 index c822d56..0000000 --- a/Screenshots/review-ground-contact.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bfbe4847a98fdbc42050406d4b26569bf910f5376994eeea3d646e71e0f36d5 -size 1748441 diff --git a/Screenshots/review-hero-facade-front.png b/Screenshots/review-hero-facade-front.png deleted file mode 100644 index 715236c..0000000 --- a/Screenshots/review-hero-facade-front.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b1dfe09f9de990c18fab1f9289119f6a98566523168b746c408d2da1aca8203c -size 2283522 diff --git a/Screenshots/review-hero-facade-oblique.png b/Screenshots/review-hero-facade-oblique.png deleted file mode 100644 index 5a4f65e..0000000 --- a/Screenshots/review-hero-facade-oblique.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ee46da9476f90434c8a676d0d4726f9bc4543d72d22b5db44493b44072ed8729 -size 1518947 diff --git a/Screenshots/review-hero-portals.png b/Screenshots/review-hero-portals.png deleted file mode 100644 index ff90639..0000000 --- a/Screenshots/review-hero-portals.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd2350c9bf572a05cd7323368a3f435b7dea5b1daaa530f9512bfea1314c79f1 -size 1328667 diff --git a/Screenshots/review-npc-prefab.png b/Screenshots/review-npc-prefab.png deleted file mode 100644 index 239857d..0000000 --- a/Screenshots/review-npc-prefab.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:555db441a3a709057db8f61b367772d5b0b8148391be3299a60f6c13bd05ac20 -size 1736426 diff --git a/Screenshots/review-overview.png b/Screenshots/review-overview.png deleted file mode 100644 index 936cdc2..0000000 --- a/Screenshots/review-overview.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:42eeaadb8795d84af81931b13fe4644601e8aadf33539b02d171e86730dc8269 -size 2622214 diff --git a/Screenshots/review-player-placement.png b/Screenshots/review-player-placement.png deleted file mode 100644 index 887dc65..0000000 --- a/Screenshots/review-player-placement.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffb788897558080e8100cdf174b0998f6b69ea4ffe0bf3d25a79d94f6aafb3d8 -size 2026302 diff --git a/Screenshots/review-scale-cubes.png b/Screenshots/review-scale-cubes.png deleted file mode 100644 index 7a713c0..0000000 --- a/Screenshots/review-scale-cubes.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bae1665389a83d7c7b7339ba626dd1eb5362322f3c4c05c93c5808aaa1fd4014 -size 1934977 diff --git a/Screenshots/review-shadow-compare.png b/Screenshots/review-shadow-compare.png deleted file mode 100644 index e0a75c2..0000000 --- a/Screenshots/review-shadow-compare.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:23457d81639ea5dcdb4fdea9af0a424a968f25b3559c30da5ede45355c6eac66 -size 1722925 diff --git a/Tools/README.md b/Tools/README.md index 97c90cb..c73337b 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -56,7 +56,9 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | Tool | Purpose | | --- | --- | | `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. | +| `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, advisory only — the model is unreliable on this content and never replaces a human verdict. | +| `visual_loop.py` | The reference-vs-render loop. `--verify` gates each capture against its expectation in `visual_targets.json` (blank/short/wrong-size/tone-less shots fail); `--compare` measures luminance, contrast, edge density, palette and 3×3 composition against the reference photograph and turns the deltas into modelling actions naming the skill to consult (`blender-modeler`, `archviz`, `materials`, `lookdev`, `camera-cinematography`); `--iteration N` writes the artefacts under `Art/reference/iterations/N/`, which is **gitignored until the owner confirms a shot**. | +| `visual_targets.json` | Per-shot expectation and reference photograph for `visual_loop.py`. Every entry must be a demo-map shot. | ## Gameplay QA diff --git a/Tools/render_screenshots.sh b/Tools/render_screenshots.sh index 1fd3bd4..6a9fd90 100755 --- a/Tools/render_screenshots.sh +++ b/Tools/render_screenshots.sh @@ -1,56 +1,54 @@ #!/usr/bin/env bash -# Render in-game screenshots for visual review into /Screenshots/ (gitignored). +# Render the demo-map screenshots used for visual review into /Screenshots/demo/. # -# Usage: Tools/render_screenshots.sh -# Rebuilds /Game/Test/ReviewMap and /Game/Hollywood/DemoMap (shot lists), runs -# the game on each map (the GigLineShotActor walks the cameras, captures, quits), -# and copies the PNGs into Screenshots/. PNGs are never committed. +# Usage: Tools/render_screenshots.sh [--verify] +# +# Only the **demo map** is captured: the review staging map exists to hold the +# hero/camera list, not to produce review screenshots. Everything here is what a +# player actually sees when the demo runs. +# +# --verify run Tools/visual_loop.py --verify afterwards and fail if a shot +# does not show the content its expectation describes +# +# The PNGs are committed (see Screenshots/README.md): a reviewer must see exactly +# what the build rendered. set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ENGINE="${UE_ENGINE_DIR:-/run/media/system/DATA/Engines/UE_5.8.4}" PROJ="$REPO/Game/GigLine/GigLine.uproject" +VERIFY="no" +[ "${1:-}" = "--verify" ] && VERIFY="yes" -echo "== 1/4 build review + demo shot maps ==" +echo "== 1/3 stage the demo shot cameras ==" cd "$ENGINE" ./Engine/Binaries/Linux/UnrealEditor-Cmd "$PROJ" \ -run=pythonscript -script="$REPO/Tools/ue_build_review_map.py" \ -nullrhi -unattended -nopause -stdout -cat /tmp/ue-review-map-result.txt | tail -3 +tail -3 /tmp/ue-review-map-result.txt 2>/dev/null || true -run_capture() { - local map="$1" label="$2" - echo "== capture: $label ==" - ./Engine/Binaries/Linux/UnrealEditor-Cmd "$PROJ" "$map" \ - -game -windowed -ResX=1920 -ResY=1080 -nopause -nosplash -stdout -} +echo "== 2/3 capture the demo map ==" +./Engine/Binaries/Linux/UnrealEditor-Cmd "$PROJ" /Game/Hollywood/DemoMap \ + -game -windowed -ResX=1920 -ResY=1080 -nopause -nosplash -stdout -echo "== 2/4 run game on review map (hero/scene shots) ==" -run_capture "/Game/Test/ReviewMap" "review" - -echo "== 3/4 run game on demo map (walkable-demo shots) ==" -run_capture "/Game/Hollywood/DemoMap" "demo" - -echo "== 4/4 collect screenshots ==" +echo "== 3/3 collect the demo screenshots ==" mkdir -p "$REPO/Screenshots/demo" -rm -f "$REPO"/Screenshots/review-*.png "$REPO"/Screenshots/demo/review-*.png +rm -f "$REPO"/Screenshots/demo/review-*.png found=0 for src in \ "$REPO/Game/GigLine/Saved/Screenshots"/*/review-*.png \ "$REPO/Game/GigLine/Saved/Screenshots"/*/*/review-*.png \ "$HOME/.config/Epic/GigLine/Saved/Screenshots"/*/review-*.png; do [ -e "$src" ] || continue - name=$(basename "$src") - if [[ "$name" == review-?("")-demo-* || "$name" == review-*-demo-* || "$name" == review-0?-demo-* ]] \ - || [[ "$name" == *demo-* ]]; then - cp -f "$src" "$REPO/Screenshots/demo/$name" - else - cp -f "$src" "$REPO/Screenshots/$name" - fi - found=1 + case "$(basename "$src")" in + *demo-*) cp -f "$src" "$REPO/Screenshots/demo/"; found=1 ;; + esac done -[ "$found" = "1" ] || { echo "ERROR: no review-*.png found"; exit 1; } -echo "--- Screenshots/ ---" -ls -1 "$REPO/Screenshots" -echo "--- Screenshots/demo/ ---" -ls -1 "$REPO/Screenshots/demo" \ No newline at end of file +[ "$found" = "1" ] || { echo "ERROR: no demo screenshots found"; exit 1; } +ls -1 "$REPO/Screenshots/demo" + +if [ "$VERIFY" = "yes" ]; then + echo "== verify: does each shot show what it should? ==" + cd "$REPO" + python3 Tools/visual_loop.py --verify +fi diff --git a/Tools/tests/test_visual_loop.py b/Tools/tests/test_visual_loop.py new file mode 100644 index 0000000..f7e4210 --- /dev/null +++ b/Tools/tests/test_visual_loop.py @@ -0,0 +1,139 @@ +"""Tests for Tools/visual_loop.py — metrics, comparison and feedback rules. + +Synthetic 8-bit images are written to a temp directory and measured, so the +numbers can be reasoned about directly (a mid-grey flat frame has near-zero edge +density and contrast; a checkerboard has a lot of both). +""" + +import os +import sys +import tempfile +import unittest + +from PIL import Image + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +sys.path.insert(0, os.path.join(ROOT, "Tools")) + +import visual_loop as vl # noqa: E402 + + +def write_image(path, kind, size=(64, 64)): + img = Image.new("RGB", size) + if kind == "flat": + img.paste((128, 128, 128), (0, 0, size[0], size[1])) + elif kind == "dark": + img.paste((10, 10, 10), (0, 0, size[0], size[1])) + elif kind == "checker": + px = img.load() + for y in range(size[1]): + for x in range(size[0]): + px[x, y] = (240, 240, 240) if (x // 4 + y // 4) % 2 else (20, 20, 20) + elif kind == "sky": + # bright top third, mid stone bottom: composition like an elevation + px = img.load() + for y in range(size[1]): + for x in range(size[0]): + px[x, y] = (150, 190, 230) if y < size[1] // 3 else (170, 165, 155) + img.save(path) + return path + + +class MetricsTest(unittest.TestCase): + def test_flat_frame_has_no_edges_and_no_contrast(self): + with tempfile.TemporaryDirectory() as tmp: + m = vl.metrics(write_image(os.path.join(tmp, "a.png"), "flat")) + self.assertEqual(m["size"], [64, 64]) + self.assertAlmostEqual(m["mean_luma"], 128.0, delta=1.0) + self.assertLess(m["rms_contrast"], 1.0) + self.assertLess(m["edge_density"], vl.MIN_EDGE_DENSITY) + + def test_checker_frame_has_edges_and_contrast(self): + with tempfile.TemporaryDirectory() as tmp: + m = vl.metrics(write_image(os.path.join(tmp, "b.png"), "checker")) + self.assertGreater(m["edge_density"], 0.01) + self.assertGreater(m["rms_contrast"], 50.0) + + def test_composition_grid_is_nine_cells(self): + with tempfile.TemporaryDirectory() as tmp: + m = vl.metrics(write_image(os.path.join(tmp, "c.png"), "sky")) + self.assertEqual(len(m["grid_luma"]), 9) + # sky row should be brighter than the stone rows + self.assertGreater(m["grid_luma"][0], m["grid_luma"][8]) + + def test_dominant_palette_is_reported(self): + with tempfile.TemporaryDirectory() as tmp: + m = vl.metrics(write_image(os.path.join(tmp, "d.png"), "flat")) + self.assertTrue(m["dominant"]) + self.assertAlmostEqual(m["dominant"][0]["share"], 1.0, delta=0.05) + + +class CompareTest(unittest.TestCase): + def _metrics(self, kind): + with tempfile.TemporaryDirectory() as tmp: + return vl.metrics(write_image(os.path.join(tmp, "x.png"), kind)) + + def test_identical_images_score_high(self): + m = self._metrics("checker") + delta = vl.compare(m, m) + self.assertGreater(delta["score"], 0.9) + self.assertEqual(delta["delta_luma"], 0.0) + + def test_flat_against_checker_scores_low_on_edges(self): + flat, checker = self._metrics("flat"), self._metrics("checker") + delta = vl.compare(flat, checker) + self.assertLess(delta["edge_ratio"], 0.5) + self.assertLess(delta["score"], 0.9) + + def test_dark_against_bright_reports_a_luma_delta(self): + dark, bright = self._metrics("dark"), self._metrics("checker") + delta = vl.compare(dark, bright) + self.assertLess(delta["delta_luma"], -50) + + +class FeedbackTest(unittest.TestCase): + def test_low_edge_ratio_names_the_modelling_skills(self): + notes = vl.feedback("x", {}, {}, { + "delta_luma": 0, "contrast_ratio": 1.0, "edge_ratio": 0.3, + "palette_distance": 10, "composition_correlation": 0.8, "score": 0.5}) + joined = " ".join(notes) + self.assertIn("Relief", joined) + self.assertIn("blender-modeler", joined) + + def test_bright_and_compressed_flags_lighting_and_lookdev(self): + notes = vl.feedback("x", {}, {}, { + "delta_luma": 40, "contrast_ratio": 0.5, "edge_ratio": 1.0, + "palette_distance": 10, "composition_correlation": 0.8, "score": 0.5}) + joined = " ".join(notes) + self.assertIn("Too bright", joined) + self.assertIn("lookdev", joined) + + def test_framing_mismatch_names_the_camera_skill(self): + notes = vl.feedback("x", {}, {}, { + "delta_luma": 0, "contrast_ratio": 1.0, "edge_ratio": 1.0, + "palette_distance": 10, "composition_correlation": 0.1, "score": 0.5}) + self.assertIn("camera-cinematography", " ".join(notes)) + + def test_no_delta_reports_no_action(self): + notes = vl.feedback("x", {}, {}, { + "delta_luma": 0, "contrast_ratio": 1.0, "edge_ratio": 1.0, + "palette_distance": 5, "composition_correlation": 0.9, "score": 0.95}) + self.assertIn("No metric is outside its tolerance", notes[0]) + + +class TargetSpecTest(unittest.TestCase): + def test_every_target_has_expectation_and_reference(self): + for target in vl.load_targets(): + self.assertTrue(target["expect"].strip(), target["name"]) + self.assertTrue(target["references"], target["name"]) + self.assertTrue(target["shot"].startswith("Screenshots/demo/"), + f"{target['name']} must be a demo-map shot") + + def test_iteration_output_is_gitignored(self): + # Iterations are deliberately uncommitted until the owner confirms a shot. + ignore = open(os.path.join(ROOT, ".gitignore"), encoding="utf-8").read() + self.assertIn("/Art/reference/iterations/", ignore) + + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/visual_loop.py b/Tools/visual_loop.py new file mode 100644 index 0000000..e0bb715 --- /dev/null +++ b/Tools/visual_loop.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python3 +"""Iterate a building against the reference photography and the engine render. + +The loop this tool implements: + +``` +reference photograph ─┐ + ├─ compare ─► metrics + feedback ─► blender-skill actions +engine screenshot ─┘ │ + └─ next iteration (uncommitted) +``` + +It answers three questions with numbers instead of opinions: + +1. **Does the shot show what it was supposed to?** (`--verify`) — every capture + has an expectation in `Tools/visual_targets.json`; a shot that is blank, the + wrong size, or wildly unlike its expectation is reported as a failure. +2. **How far is the render from the reference?** (`--compare`) — luminance, + contrast, edge density, palette and 3×3 composition are measured on both and + reported as deltas with a 0–1 similarity score. +3. **What should be modelled next?** (`--feedback`) — the deltas are translated + into concrete actions phrased for the Blender skill packs (silhouette, relief, + value range, hue, framing). + +Iterations are **not committed**: every artefact goes under +`Art/reference/iterations//`, which is gitignored. Promote a shot into +`Screenshots/` only after the owner confirms it. + + python3 Tools/visual_loop.py --verify # gate the current set + python3 Tools/visual_loop.py --compare --iteration 3 # measure vs references + python3 Tools/visual_loop.py --compare --iteration 3 --judge # + vision advisory + +Metrics are computed with Pillow (already in requirements-tools.txt); no numpy. +""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import sys + +from PIL import Image, ImageFilter, ImageStat + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +TARGETS = os.path.join(ROOT, "Tools", "visual_targets.json") +ITER_ROOT = os.path.join(ROOT, "Art", "reference", "iterations") + +# A shot must clear these floors or it is treated as broken, not merely ugly. +MIN_EDGE_DENSITY = 0.010 +MIN_CONTRAST = 12.0 +MIN_MEAN_LUMA = 8.0 + + +# --- metrics ---------------------------------------------------------------- + + +def _luma_gray(image): + return image.convert("L") + + +def metrics(image_path): + """Measure one image. Everything is on the 8-bit luma/RGB 0..255 scale.""" + with Image.open(image_path) as handle: + image = handle.convert("RGB") + width, height = image.size + gray = _luma_gray(image) + stat = ImageStat.Stat(gray) + + # Edge density: fraction of pixels above a low threshold after FIND_EDGES. + # Stands in for "how much relief and silhouette detail is in the frame". + # Filter, then crop: FIND_EDGES samples outside the image as zero, so it leaves + # its own border ring at a high value (236 of 3600 px on a 60x60 frame, 6.5%), + # which would make a perfectly flat picture look detailed. Cropping *before* + # filtering does not help — the new border gets the same treatment. + filtered = gray.filter(ImageFilter.FIND_EDGES) + inner = filtered.crop((2, 2, max(3, width - 2), max(3, height - 2))) + hist = inner.histogram() + area = float(inner.width * inner.height) + edge_pixels = sum(hist[24:]) + edge_density = edge_pixels / area if area else 0.0 + + # Palette: quantise and keep the dominant colours with their share. + total = float(width * height) + quantised = image.quantize(colors=12, method=Image.Quantize.MEDIANCUT) + palette = quantised.getpalette() or [] + counts = sorted(quantised.getcolors() or [], reverse=True) + dominant = [] + for count, index in counts[:5]: + base = index * 3 + if base + 2 < len(palette): + dominant.append({ + "rgb": palette[base:base + 3], + "share": round(count / total, 3), + }) + + # 3x3 composition grid of mean luma, so "too much sky" is measurable. + grid = [] + for row in range(3): + for col in range(3): + box = (int(col * width / 3), int(row * height / 3), + int((col + 1) * width / 3), int((row + 1) * height / 3)) + grid.append(round(ImageStat.Stat(gray.crop(box)).mean[0], 1)) + + return { + "path": os.path.relpath(image_path, ROOT), + "size": [width, height], + "mean_luma": round(stat.mean[0], 1), + "rms_contrast": round(stat.stddev[0], 1), + "edge_density": round(edge_density, 4), + "dominant": dominant, + "grid_luma": grid, + } + + +def _palette_distance(a, b): + """Mean nearest-colour distance between two dominant palettes, 0..441.""" + if not a or not b: + return None + total = 0.0 + for entry in a: + pa = entry["rgb"] + best = min( + sum((pa[i] - pb["rgb"][i]) ** 2 for i in range(3)) ** 0.5 for pb in b) + total += best + return round(total / len(a), 1) + + +def _correlation(values_a, values_b): + n = len(values_a) + mean_a = sum(values_a) / n + mean_b = sum(values_b) / n + var_a = sum((v - mean_a) ** 2 for v in values_a) + var_b = sum((v - mean_b) ** 2 for v in values_b) + if var_a <= 0.0 or var_b <= 0.0: + return 0.0 + cov = sum((a - mean_a) * (b - mean_b) for a, b in zip(values_a, values_b)) + return round(cov / (var_a * var_b) ** 0.5, 3) + + +def compare(shot_metrics, reference_metrics): + """Delta between a render and a reference, plus a 0..1 similarity score.""" + luma = shot_metrics["mean_luma"] - reference_metrics["mean_luma"] + contrast_ratio = (shot_metrics["rms_contrast"] / + reference_metrics["rms_contrast"]) if reference_metrics["rms_contrast"] else 1.0 + edge_ratio = (shot_metrics["edge_density"] / + reference_metrics["edge_density"]) if reference_metrics["edge_density"] else 1.0 + palette = _palette_distance(shot_metrics["dominant"], reference_metrics["dominant"]) + composition = _correlation(shot_metrics["grid_luma"], reference_metrics["grid_luma"]) + + # Score: luminance within 25, contrast and edge ratios near 1, palette near 0, + # composition correlated. Each term is clamped to 0..1 and equally weighted. + def near(value, target, span): + return max(0.0, 1.0 - abs(value - target) / span) + + score = ( + near(luma, 0.0, 25.0) + + near(contrast_ratio, 1.0, 1.0) + + near(edge_ratio, 1.0, 1.5) + + (1.0 if palette is None else near(palette, 0.0, 120.0)) + + max(0.0, composition) + ) / 5.0 + + return { + "delta_luma": round(luma, 1), + "contrast_ratio": round(contrast_ratio, 2), + "edge_ratio": round(edge_ratio, 2), + "palette_distance": palette, + "composition_correlation": composition, + "score": round(score, 3), + } + + +# --- feedback --------------------------------------------------------------- +# Each rule maps a measured delta to an action phrased for the Blender skills. + + +def feedback(name, shot, reference, delta): + notes = [] + if delta["edge_ratio"] < 0.6: + notes.append( + f"**Relief and silhouette detail** are {1 - delta['edge_ratio']:.0%} below " + f"the reference (`blender-modeler` / `archviz`): add window rhythm, " + f"pilasters, cornices or quoins that cast a shadow line.") + elif delta["edge_ratio"] > 1.8: + notes.append( + "**Too busy** against the reference: the render has far more edge detail. " + "Check for stray kit pieces, z-fighting or duplicated geometry.") + + if delta["delta_luma"] <= -15: + notes.append( + f"**Too dark** by {abs(delta['delta_luma']):.0f} luma (`materials` / " + f"`lighting`): raise the material albedo or the exposure.") + elif delta["delta_luma"] >= 15: + notes.append( + f"**Too bright** by {delta['delta_luma']:.0f} luma (`materials` / " + f"`lighting`): lower the albedo or the exposure.") + + if delta["contrast_ratio"] < 0.7: + notes.append( + "**Value range compressed** (`lookdev`): the reference has much stronger " + "light/shadow separation. Deepen recesses or widen the material range.") + elif delta["contrast_ratio"] > 1.5: + notes.append( + "**Value range too harsh** (`lookdev`): soften the key or the shadow " + "contrast so the surface does not clip.") + + if delta["palette_distance"] is not None and delta["palette_distance"] > 60: + notes.append( + f"**Hue drift** ({delta['palette_distance']:.0f} palette distance, " + f"`materials`): compare the dominant colours in the iteration report " + f"against the reference and correct the base colours.") + + if delta["composition_correlation"] < 0.3: + notes.append( + "**Framing differs** (`camera-cinematography`): the 3x3 luma grid does " + "not match the reference. Check the camera height, lens and how much " + "sky/ground is in frame before changing the model.") + + if not notes: + notes.append( + "No metric is outside its tolerance. Differences left are stylistic; " + "confirm visually rather than by number.") + return notes + + +# --- verification ----------------------------------------------------------- + + +def verify(targets, judge=False): + """Check that each shot exists, is not broken, and looks like its expectation.""" + results = [] + for target in targets: + shot = os.path.join(ROOT, target["shot"]) + entry = {"name": target["name"], "shot": target["shot"], "expect": target["expect"]} + if not os.path.exists(shot): + entry["status"] = "MISSING" + results.append(entry) + continue + m = metrics(shot) + problems = [] + if m["edge_density"] < MIN_EDGE_DENSITY: + problems.append(f"almost flat (edge density {m['edge_density']})") + if m["rms_contrast"] < MIN_CONTRAST: + problems.append(f"no tonal range (contrast {m['rms_contrast']})") + if m["mean_luma"] < MIN_MEAN_LUMA: + problems.append(f"black frame (luma {m['mean_luma']})") + expected_size = target.get("size") + if expected_size and m["size"] != expected_size: + problems.append(f"size {m['size']} != {expected_size}") + entry["metrics"] = {k: m[k] for k in + ("size", "mean_luma", "rms_contrast", "edge_density")} + entry["status"] = "FAIL" if problems else "OK" + entry["problems"] = problems + + if judge and os.environ.get("OPENROUTER_API_KEY") or ( + judge and os.path.exists(os.path.join(ROOT, ".env"))): + entry["judge"] = run_judge(shot, target["expect"]) + results.append(entry) + return results + + +def run_judge(shot, expect): + """Advisory vision check. Never a gate: the model is unreliable on this content.""" + import subprocess + try: + proc = subprocess.run( + [sys.executable, os.path.join(ROOT, "Tools", "judge_screenshot.py"), + "--image", shot, "--expect", expect], + capture_output=True, text=True, timeout=180, cwd=ROOT) + out = (proc.stdout or "") + (proc.stderr or "") + for line in out.splitlines(): + if line.startswith("verdict="): + return line.strip() + return "unavailable" + except Exception as exc: # noqa: BLE001 - advisory only + return f"error: {exc}" + + +# --- reporting -------------------------------------------------------------- + + +def write_iteration(iteration, rows, args): + """Write the iteration artefacts (gitignored) and return the directory.""" + out_dir = os.path.join(ITER_ROOT, str(iteration)) + os.makedirs(out_dir, exist_ok=True) + for row in rows: + shot = os.path.join(ROOT, row["shot"]) + if os.path.exists(shot): + shutil.copy2(shot, os.path.join(out_dir, os.path.basename(shot))) + with open(os.path.join(out_dir, "metrics.json"), "w", encoding="utf-8") as handle: + json.dump({"iteration": iteration, "targets": rows}, handle, indent=2) + + lines = [f"# Iteration {iteration} — reference vs render", ""] + for row in rows: + lines.append(f"## {row['name']}") + lines.append("") + lines.append(f"- shot: `{row['shot']}`") + if row.get("reference"): + lines.append(f"- reference: `{row['reference']}`") + delta = row.get("delta") + if delta: + lines.append( + f"- **score {delta['score']:.2f}** · luma {delta['delta_luma']:+.0f} · " + f"contrast ×{delta['contrast_ratio']} · edges ×{delta['edge_ratio']} · " + f"palette {delta['palette_distance']} · composition r={delta['composition_correlation']}") + lines.append("") + for note in row.get("feedback", []): + lines.append(f"- {note}") + lines.append("") + path = os.path.join(out_dir, "feedback.md") + with open(path, "w", encoding="utf-8") as handle: + handle.write("\n".join(lines) + "\n") + return out_dir, path + + +def load_targets(): + with open(TARGETS, encoding="utf-8") as handle: + return json.load(handle)["targets"] + + +def main(argv=None) -> int: + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--verify", action="store_true", + help="gate the current shots against their expectations") + ap.add_argument("--compare", action="store_true", + help="measure each shot against its reference(s)") + ap.add_argument("--feedback", action="store_true", help="include action notes (default with --compare)") + ap.add_argument("--judge", action="store_true", help="add the advisory vision verdict") + ap.add_argument("--iteration", default=None, help="iteration label for the output directory") + args = ap.parse_args(argv) + + if not (args.verify or args.compare): + args.verify = args.compare = True + + targets = load_targets() + + if args.verify: + print("== verification ==") + results = verify(targets, judge=args.judge) + failed = [r for r in results if r["status"] != "OK"] + for r in results: + extra = f" ({'; '.join(r['problems'])})" if r.get("problems") else "" + judge = f" [judge: {r['judge']}]" if r.get("judge") else "" + print(f" {r['status']:7} {r['name']:32}{extra}{judge}") + print(f" -> {len(results) - len(failed)}/{len(results)} shots OK") + + if args.compare: + print("\n== comparison ==") + rows = [] + for target in targets: + shot_path = os.path.join(ROOT, target["shot"]) + if not os.path.exists(shot_path): + continue + shot_m = metrics(shot_path) + best = None + for ref_rel in target.get("references", []): + ref_path = os.path.join(ROOT, ref_rel) + if not os.path.exists(ref_path): + continue + ref_m = metrics(ref_path) + delta = compare(shot_m, ref_m) + if best is None or delta["score"] > best[1]["score"]: + best = (ref_rel, delta, ref_m) + if best is None: + continue + ref_rel, delta, ref_m = best + rows.append({ + "name": target["name"], + "shot": target["shot"], + "reference": ref_rel, + "delta": delta, + "feedback": feedback(target["name"], shot_m, ref_m, delta), + "expect": target["expect"], + }) + print(f" {target['name']:32} score {delta['score']:.2f} " + f"luma {delta['delta_luma']:+.0f} edges ×{delta['edge_ratio']}") + if args.iteration is not None: + out_dir, feedback_path = write_iteration(args.iteration, rows, args) + print(f"\n iteration written to {os.path.relpath(out_dir, ROOT)}") + print(f" feedback: {os.path.relpath(feedback_path, ROOT)}") + print(" (gitignored: promote a shot into Screenshots/ only after owner confirmation)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/visual_targets.json b/Tools/visual_targets.json new file mode 100644 index 0000000..c3e0665 --- /dev/null +++ b/Tools/visual_targets.json @@ -0,0 +1,53 @@ +{ + "comment": "What each demo-map capture must show, and which reference photograph it is measured against. Used by Tools/visual_loop.py --verify (does the shot show the expected content?) and --compare (how far is the render from the reference?).", + "targets": [ + { + "name": "demo-spawn-view", + "shot": "Screenshots/demo/review-demo-spawn-view.png", + "size": [ + 1920, + 1080 + ], + "references": [ + "Art/reference/hollywood/el-capitan-facade-day-wikimedia-2009-10-27.jpg" + ], + "expect": "The whole south block from the north sidewalk: the neoclassical hero facade between its neighbouring buildings, both sidewalks and the road in the foreground, daylight" + }, + { + "name": "demo-street-level", + "shot": "Screenshots/demo/review-demo-street-level.png", + "size": [ + 1920, + 1080 + ], + "references": [ + "Art/reference/hollywood/el-capitan-theatre-blade-sign-day-wikimedia-2008-10.jpg" + ], + "expect": "Street level from the north side: the hero colonnade with the adjacent frontage buildings either side, shopfronts at pavement level, daylight" + }, + { + "name": "demo-crossing", + "shot": "Screenshots/demo/review-demo-crossing.png", + "size": [ + 1920, + 1080 + ], + "references": [ + "Art/reference/hollywood/walk-of-fame-terrazzo-ovation-day-cc0-2019-06-20.jpg" + ], + "expect": "The designated mid-block crossing: continental zebra bars on the asphalt, the south frontage and hero colonnade behind, daylight" + }, + { + "name": "demo-north-buildings", + "shot": "Screenshots/demo/review-demo-north-buildings.png", + "size": [ + 1920, + 1080 + ], + "references": [ + "Art/reference/north-chinese/01-grauman-s-chinese-theatre-by-carol-highsmith.jpg" + ], + "expect": "The two north-side buildings (TCL Chinese Theatre, Madame Tussaud's) seen from across Hollywood Boulevard, full height in frame, daylight" + } + ] +} \ No newline at end of file diff --git a/requirements-tools.txt b/requirements-tools.txt index b18d513..597bf74 100644 --- a/requirements-tools.txt +++ b/requirements-tools.txt @@ -1 +1 @@ -requests>=2.28 +Pillow>=9.2 -- 2.47.3 From ec5545b2657a72c538fc8e2c5cc8affb14ff6f4d Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 20:03:34 +0100 Subject: [PATCH 09/13] feat(theatre): marquee canopy and blade sign; fix two placement bugs Tools/theatre_trim_build.py builds the El Capitan Theatre's signature features in world coordinates - projecting marquee (12.0 x 2.60 m canopy, 4.60 m soffit), illuminated fascia, recessed soffit, vertical blade sign (1.30 x 5.20 m) with frame and bracket - with a JSON part report. Tools/ue_stage_theatre_trim.py imports and stages one actor per piece with site materials, clearing a previous run first. Two bugs, neither visible in a render: - Axis sign: Blender +Y is Unreal -Y, so the theatre face at Unreal Y +1001 is Blender Y -10.01; using +10.01 mirrored the marquee across the boulevard. - Absolute Z: trim heights ignored the 4.56 m pavement, leaving the canopy 5 cm above the ground. GROUND_Z now offsets every height. QA wall probe: the Y 1000 scan row sat ~1 cm from the building line and grazed the theatre wall after the kit's proud shopfronts; rows now sample the street envelope only. Packaged QA back to PASS (transect 0 holes, wall probe 0/35, facade probe 0/9). Screenshots re-rendered and verified 4/4. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Theatre/SM_Trim_BladeSign.uasset | 3 + .../Theatre/SM_Trim_BladeSignBracket.uasset | 3 + .../Theatre/SM_Trim_BladeSignFrame.uasset | 3 + .../Theatre/SM_Trim_MarqueeCanopy.uasset | 3 + .../Theatre/SM_Trim_MarqueeFascia.uasset | 3 + .../Theatre/SM_Trim_MarqueeSoffit.uasset | 3 + Game/GigLine/Content/Test/ReviewMap.umap | 2 +- .../Source/GigLine/GigLineCharacter.cpp | 6 +- MEMORY.md | 18 ++ Screenshots/demo/review-demo-crossing.png | 4 +- .../demo/review-demo-north-buildings.png | 4 +- Screenshots/demo/review-demo-spawn-view.png | 4 +- Screenshots/demo/review-demo-street-level.png | 4 +- TODO.md | 2 +- Tools/README.md | 2 + Tools/theatre_trim_build.py | 208 ++++++++++++++++++ Tools/ue_stage_theatre_trim.py | 169 ++++++++++++++ 18 files changed, 432 insertions(+), 13 deletions(-) create mode 100644 Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset create mode 100644 Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset create mode 100644 Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset create mode 100644 Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset create mode 100644 Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset create mode 100644 Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset create mode 100644 Tools/theatre_trim_build.py create mode 100644 Tools/ue_stage_theatre_trim.py diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index aa39faa..2b1a82e 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:78027bd7ca19b89d4c8026c8429d6dde5a220427c2627de2c50a19e7686b9e03 -size 949399 +oid sha256:9ca76190a48710324d82892f5f56e423f123156f1b8e83740e37dbe3f7aa418d +size 958399 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset new file mode 100644 index 0000000..9805a83 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:360a19294a103b52b4c0b1e5af5befd9a55fc9471443a703f6e39948d9eed962 +size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset new file mode 100644 index 0000000..7b89f33 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:262d2eaac6f9e2ec2cd5217c68cd9ac4882e075b5d52e6d09dcc96e3886f56a8 +size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset new file mode 100644 index 0000000..990106b --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:516633d806cd4c97bc409b27ffea4533211a798e3811c45c2d6f09176ebd6fc5 +size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset new file mode 100644 index 0000000..6ce02c6 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e55efd7aee1bb44a6968bf9f82a431798631d20bfafa53d4198c9d436ca4848e +size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset new file mode 100644 index 0000000..5c3ac79 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9554845054a637f8101c6d4fe9f85bce8fd7fe5a532768f4b345151b628a5f6d +size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset new file mode 100644 index 0000000..33e2f67 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f89567a63c72fde986c42ffcb0577b672ece498d81159250c985fd1915a7863 +size 72413 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 8babc6b..562ce47 100644 --- a/Game/GigLine/Content/Test/ReviewMap.umap +++ b/Game/GigLine/Content/Test/ReviewMap.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f66abdc90461601dfb4fe45f6afd16d7cf80965beaecf06c3377c6b2c75ed5e7 +oid sha256:3d7897fdd23c11963990f71530a4630f8e2067503498c5906b13e232a79819e7 size 28811 diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index aafab92..4a679e7 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -379,7 +379,11 @@ void AGigLineCharacter::StartQASelfTest() // trace at a fixed Z (e.g. 160) is wrong: the street is at Z ~366 // there, so a 160 cm probe starts inside the earth (dist 0). int32 Walls = 0, WallSamples = 0; - static const float TraceYs[] = {-1400.0f, -167.0f, 1000.0f, -1382.0f, 1047.0f}; + // Sample the street envelope only: the carriageway plus the walkable + // part of each sidewalk. Y 1000 used to be in this list and sits ~1 cm + // from the frontage building line, so it grazed the theatre wall once + // the kit's proud shopfronts went in - a probe artefact, not a blocker. + static const float TraceYs[] = {-1400.0f, -167.0f, 800.0f, -1382.0f, 718.0f}; for (float WallY : TraceYs) { for (float X0 = -19500.0f; X0 <= -3000.0f; X0 += 2500.0f) diff --git a/MEMORY.md b/MEMORY.md index cca7e7e..aa23073 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -650,3 +650,21 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. north buildings 0.34. Edge density is the dominant gap everywhere (0.04–0.11 against 0.20–0.32 in the references) — the render still lacks the relief the photographs carry, which is exactly what the `0.1.4` façade work is for. + +## 2026-11-09 — theatre marquee + blade sign + +- `Tools/theatre_trim_build.py` builds the El Capitan Theatre's signature features in + **world coordinates**: marquee canopy 12.0 × 2.60 m at a 4.60 m soffit, illuminated + fascia, recessed soffit, vertical blade sign 1.30 × 5.20 m with frame and bracket. + `Tools/ue_stage_theatre_trim.py` imports to `/Game/Hollywood/Theatre` and stages one + actor per piece (single spawn at the origin — no placement arithmetic). +- **Two placement bugs, both invisible in a render:** + 1. **Axis sign.** Blender +Y is Unreal −Y, so a face at Unreal Y +1001 is Blender + Y −10.01. Using +10.01 put the whole marquee on the far side of the boulevard. + 2. **Absolute Z.** Trim heights were absolute while the pavement at the theatre is + 4.56 m above the project datum, so the canopy hung 5 cm above the pavement — the + QA wall probe caught it as a knee-height blocker. `GROUND_Z` now offsets every + height. +- Wall probe: a scan row at Y 1000 sat ~1 cm from the frontage building line and + grazed the theatre wall once the kit's proud shopfronts went in. Rows now sample the + street envelope only (−1400, −167, 800, −1382, 718). diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 91ed79e..6a19fc7 100644 --- a/Screenshots/demo/review-demo-crossing.png +++ b/Screenshots/demo/review-demo-crossing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65530cfd12bc783e9a40af40b91926abf09156d27e0ae57f02b625eec8fc96bc -size 3078175 +oid sha256:9cd5184c2c1f932e5262322c6ee21d9fae24f4af534ed6b641c11e0cd722952b +size 3079101 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index c31577c..de0eca2 100644 --- a/Screenshots/demo/review-demo-north-buildings.png +++ b/Screenshots/demo/review-demo-north-buildings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a16d37433312a7e5ee2fecbaba5bee822566a58a1a7c45e6bb7c05c3b7c76689 -size 2443382 +oid sha256:153b25b43f293ebf793fa8d0d685b0ef0be2a94267a03937180e4bcd602ed561 +size 2442501 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 900adca..da3f6bf 100644 --- a/Screenshots/demo/review-demo-spawn-view.png +++ b/Screenshots/demo/review-demo-spawn-view.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0145fc8e60aff6d7981c74f950f06ccb31fac4b85fdc80f58ec9aaa50397d8d -size 2942005 +oid sha256:9feb51f299584f7c7e76e69bc1e05e381b9cad381e5b0375e3430c1ea5fcaac6 +size 2946077 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index d4a549d..77452f8 100644 --- a/Screenshots/demo/review-demo-street-level.png +++ b/Screenshots/demo/review-demo-street-level.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7c2afd07d9d24aa196a8151f66452f532e10a8f52bc9f232d64a61c783a6189 -size 2863148 +oid sha256:2fdec8a67016806bbff3591f449bc16c19df35df2ae98cb18c38c15d0b91a6a5 +size 2865770 diff --git a/TODO.md b/TODO.md index 44c535c..5af1dab 100644 --- a/TODO.md +++ b/TODO.md @@ -20,7 +20,7 @@ Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the faç - [A] **Replace placeholder materials.** **In progress 2026-11-09.** `ensure_material()` now builds masonry coursing (world-space course height + joint band + tint, corrected for UE's centimetre WorldPosition) and roughness variation; new `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5 without a refraction pass) and `M_FacadeBanner`; assignment is per **slot** keyed on the imported slot name (it was slot-0 only, which on the hero is the banner, so stone/glass/shadow were still flat). Hero: 4/4 slots, frontage and north buildings 2/2. Remaining: bake real texture maps (brick/stone/asphalt generators) rather than graph-only detail, and give the frontage shopfront line its own material. - [x] **Give OSM buildings a façade style.** **Closed as superseded 2026-11-09.** The Blosm `setupScript` route would build a second, competing façade system next to the module kit defined in the previous task. The kit already gives every staged building a real elevation (shopfront line, window rhythm, pilasters, cornice, parapet) from a single module, so a Blosm style script would duplicate it with a different vocabulary and a different set of assumptions. Revisit only if the kit cannot express a building the photographs demand. - [ ] **Hero façade detail pass.** Ionic capitals and fluting, the parapet motto band and medallions as geometry or decal, window rhythm behind the colonnade, and the three portal flights at their photographed proportions. -- [ ] **El Capitan Theatre (6838) and the retail neighbours.** Model from the 18-frame 5472×3648 reference set; the theatre shares the measured 4.96 m bay rhythm with the temple. +- [A] **El Capitan Theatre (6838) and the retail neighbours.** **Theatre trim done 2026-11-09:** `Tools/theatre_trim_build.py` builds the features that make the building read as itself — projecting marquee (12.0 × 2.60 m canopy at a 4.60 m soffit), illuminated fascia, recessed soffit, and a vertical blade sign (1.30 × 5.20 m) with frame and bracket; `Tools/ue_stage_theatre_trim.py` imports and stages it with site materials. Two bugs fixed en route: the trim was mirrored to the far side of the boulevard (Blender +Y is Unreal −Y) and the marquee sat 5 cm above the pavement (absolute Z where the pavement is 4.56 m above the datum). **Still open:** the retail neighbours modelled from the 18-frame reference set. - [ ] Review every façade change with `Tools/render_screenshots.sh` before tagging. - [ ] Tag `v0.1.4` and write `CHANGELOG.md` entry; then remove this section. diff --git a/Tools/README.md b/Tools/README.md index c73337b..2ef7335 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -18,6 +18,8 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | `check-lfs.sh` | Fails if the `lfs` filter is missing or an LFS-tracked file was committed as a plain blob | | `facade_kit_build.py` | Builds the reusable façade **module kit** at the measured 4.96 m bay: pilaster, sill, lintel, window bay (surround + glazing), shopfront bay (piers, glass, fascia), door bay (jambs, door, threshold), cornice and parapet + coping. Exports each piece with the shared preset plus a JSON part report (dims + evidence per piece). | | `ue_import_facade_kit.py` | Imports the kit into `/Game/Hollywood/Kit/*` as static meshes with complex-as-simple collision; writes `/tmp/gigline/kit-import.txt`. | +| `theatre_trim_build.py` | El Capitan Theatre trim: projecting marquee canopy, illuminated fascia, recessed soffit, vertical blade sign with frame and bracket. Built **in world coordinates** (span and pavement height baked in) with a JSON part report, so staging is a single spawn at the origin. | +| `ue_stage_theatre_trim.py` | Imports the trim to `/Game/Hollywood/Theatre` and stages one actor per piece in the DemoMap with site materials; clears a previous run first. | | `ue_apply_facade_kit.py` | Lays the kit over a building's street face from its own bounding box: bay count = round(width / 4.96), storeys from the height, shopfronts on the ground floor, windows above, pilasters on bay boundaries, cornice and parapet at the top. Clears a previous application first; unique labels per building. | | `generate_facade_textures.py` | Deterministic stdlib PNG generator for the facade maps (`T_StoneAshlar`, `T_Stucco`, `T_Asphalt`, `T_Concrete`, each `_BC` base colour + `_R` roughness). Pure-zlib writer, hash-based value noise, identical bytes on every run; `--size` and `--out` (`/tmp/gigline/textures` by default). `ue_build_demo_map.py` imports them into `/Game/Hollywood/Textures` and samples them world-aligned. | | `ue_common.py` | Shared helpers for the Unreal commandlet scripts: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False silently), and `require_saved()` makes that failure explicit. | diff --git a/Tools/theatre_trim_build.py b/Tools/theatre_trim_build.py new file mode 100644 index 0000000..309bdfc --- /dev/null +++ b/Tools/theatre_trim_build.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""El Capitan Theatre (6838) trim: marquee canopy and blade sign. + +The theatre blockout is a plain volume; what makes it read as *the* El Capitan +Theatre is the projecting marquee over the pavement and the vertical blade sign on +the corner. Both are built **in world coordinates** here, so the export carries its +position and the Unreal staging step is a single spawn at the origin — the same +convention as the block frontage and the north buildings. + +The building's verified span (from `Geo/verification/2026-11-06-el-capitan-block.md` +and the staged mesh) is X −12082..−8210, street face at Y ≈ +1001, height 20.55 m. + + blender -b --factory-startup --python Tools/theatre_trim_build.py -- \ + --out /tmp/gigline/theatre --report /tmp/gigline/theatre-report.json + +Everything the photographs do not resolve at this scale (exact marquee depth, +soffit height, sign lettering) is an assumption recorded in the part report. +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import sys + +import bpy + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import fbx_export_preset as fbx_preset # noqa: E402 + +# Verified building span, metres (Unreal X, Y, Z but Blender-frame: X east, +# Y north, Z up, metres). +THEATRE_X0 = -120.82 +THEATRE_X1 = -82.10 +# Street face of the blockout volume, in BLENDER metres. Blender's +Y is north and +# Unreal's is -Y, so the theatre's Unreal face at Y +1001 cm is Blender Y -10.01 m. +# Getting this sign wrong put the whole marquee on the far side of the boulevard. +FACE_Y = -10.01 +HEIGHT = 20.55 +# Ground level at the theatre frontage, in the shared frame (Z up, metres). The +# pavement here is 4.56 m above the project datum, so every trim height below is +# measured from the pavement and then offset: the first build used absolute Z and +# put the marquee canopy 5 cm above the pavement, where the wall probe found it at +# knee height. +GROUND_Z = 4.56 + +# Marquee: centred on the frontage, projecting over the pavement. +MARQUEE_W = 12.0 +MARQUEE_DEPTH = 2.60 +MARQUEE_Z = GROUND_Z + 4.60 # underside of the canopy above the pavement +CANOPY_T = 0.30 +FASCIA_H = 1.10 + +BLADE_W = 1.30 +BLADE_H = 5.20 +BLADE_Z0 = GROUND_Z + 5.20 +BLADE_PROJECT = 2.00 + +PARTS: list[dict] = [] + + +def make_material(name, value, rough=0.6): + mat = bpy.data.materials.new(name) + mat.use_nodes = True + bsdf = mat.node_tree.nodes.get("Principled BSDF") + bsdf.inputs["Base Color"].default_value = (value, value * 0.97, value * 0.92, 1.0) + bsdf.inputs["Roughness"].default_value = rough + return mat + + +def validate(obj, tri_budget=6000): + import bmesh + + bm = bmesh.new() + bm.from_mesh(obj.data) + bm.edges.ensure_lookup_table() + bad = [e for e in bm.edges if len(e.link_faces) != 2] + loose = [v for v in bm.verts if not v.link_edges] + tris = sum(len(f.verts) - 2 for f in bm.faces) + bm.free() + if bad or loose: + print(f"[trim] WARNING {obj.name}: {len(bad)} non-manifold, {len(loose)} loose") + if tris > tri_budget: + print(f"[trim] WARNING {obj.name}: {tris} tris over budget") + return {"manifold": not bad, "loose_verts": len(loose), "tris": tris} + + +def note(obj, text): + PARTS.append({ + "object": obj.name, + "asset": "SM_" + obj.name, + "dims_m": [round(d, 3) for d in obj.dimensions], + "location_m": [round(c, 3) for c in obj.location], + "topology": validate(obj), + "evidence": text, + }) + print(f"[trim] {obj.name:22} {obj.dimensions.x:6.2f} x {obj.dimensions.y:5.2f} " + f"x {obj.dimensions.z:5.2f} m ({text})") + + +def cube(name, x, y, z, dx, dy, dz, material): + bpy.ops.mesh.primitive_cube_add(size=1.0, location=(x, y, z)) + obj = bpy.context.active_object + obj.name = name + obj.scale = (dx, dy, dz) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + obj.data.materials.append(material) + return obj + + +def clear_scene(): + bpy.ops.object.select_all(action="SELECT") + bpy.ops.object.delete(use_global=False) + for mesh in list(bpy.data.meshes): + if mesh.users == 0: + bpy.data.meshes.remove(mesh) + + +def build(): + """Marquee canopy, fascia, soffit and the blade sign, in world metres.""" + clear_scene() + stone = make_material("M_Trim_Stone", 0.62) + dark = make_material("M_Trim_Shadow", 0.30, rough=0.75) + lightbox = make_material("M_Trim_Lightbox", 0.86, rough=0.35) + + cx = (THEATRE_X0 + THEATRE_X1) / 2.0 + # The street is at smaller Unreal Y than the face, which is *larger* Blender Y, + # so the marquee projects in +Y here. + y_outer = FACE_Y + MARQUEE_DEPTH + + parts = [] + canopy = cube("Trim_MarqueeCanopy", cx, FACE_Y + MARQUEE_DEPTH / 2.0, + MARQUEE_Z + CANOPY_T / 2.0, MARQUEE_W, MARQUEE_DEPTH, CANOPY_T, stone) + parts.append((canopy, "photographs: projecting marquee over the pavement; " + "12.0 x 2.60 m at 4.60 m soffit — depth and height assumed")) + + fascia = cube("Trim_MarqueeFascia", cx, y_outer - 0.12, + MARQUEE_Z + CANOPY_T + FASCIA_H / 2.0, + MARQUEE_W, 0.24, FASCIA_H, lightbox) + parts.append((fascia, "photographs: illuminated fascia band around the marquee " + "edge; 1.10 m depth assumed")) + + soffit = cube("Trim_MarqueeSoffit", cx, FACE_Y + MARQUEE_DEPTH / 2.0, + MARQUEE_Z - 0.05, MARQUEE_W - 0.4, MARQUEE_DEPTH - 0.4, 0.10, dark) + parts.append((soffit, "assumption: recessed soffit panel under the canopy; " + "reads as shadow from the pavement")) + + # Blade sign on the west corner, projecting perpendicular to the facade. + bx = THEATRE_X0 + 3.6 + blade = cube("Trim_BladeSign", bx, FACE_Y + BLADE_PROJECT / 2.0, + BLADE_Z0 + BLADE_H / 2.0, BLADE_W, 0.42, BLADE_H, lightbox) + parts.append((blade, "photographs: vertical blade sign on the corner; " + "1.30 x 5.20 m, 2.00 m projection assumed")) + + frame = cube("Trim_BladeSignFrame", bx, FACE_Y + BLADE_PROJECT / 2.0, + BLADE_Z0 + BLADE_H / 2.0, BLADE_W + 0.28, 0.60, 0.35, stone) + parts.append((frame, "assumption: stone frame band at the base of the blade")) + + # Bracket back to the wall so the sign does not float. + bracket = cube("Trim_BladeSignBracket", bx, FACE_Y + BLADE_PROJECT - 0.6, + BLADE_Z0 + BLADE_H - 0.5, 0.60, 1.40, 0.30, dark) + parts.append((bracket, "assumption: bracket tying the blade back to the facade")) + + return parts + + +def export_objects(parts, out_dir): + count = 0 + for obj, _text in parts: + bpy.ops.object.select_all(action="DESELECT") + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + path = os.path.join(out_dir, "SM_" + obj.name + ".fbx") + fbx_preset.export_static(path, selection=True) + count += 1 + return count + + +def main(argv=None) -> int: + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--out", default="/tmp/gigline/theatre") + ap.add_argument("--report", default=None) + args = ap.parse_args(argv) + os.makedirs(args.out, exist_ok=True) + + parts = build() + for obj, text in parts: + note(obj, text) + exported = export_objects(parts, args.out) + print(f"[trim] exported {exported} object(s) to {args.out}") + + if args.report: + with open(args.report, "w", encoding="utf-8") as handle: + json.dump({ + "building": "El Capitan Theatre (6838)", + "span_m": [THEATRE_X0, THEATRE_X1, FACE_Y, HEIGHT], + "parts": PARTS, + }, handle, indent=2) + print(f"[trim] report -> {args.report}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/ue_stage_theatre_trim.py b/Tools/ue_stage_theatre_trim.py new file mode 100644 index 0000000..5448ddc --- /dev/null +++ b/Tools/ue_stage_theatre_trim.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Import and stage the El Capitan Theatre trim (marquee + blade sign). + +Two steps, both idempotent: + +1. Import `/tmp/gigline/theatre/*.fbx` into `/Game/Hollywood/Theatre`. +2. Stage one static-mesh actor per piece at the **origin**, because the meshes are + built in world coordinates (same convention as the block frontage and the north + buildings). Anything that has to be positioned by arithmetic is a bug waiting to + happen — the kit was once placed 12 km away that way. + + UnrealEditor-Cmd -run=pythonscript \ + -script=Tools/ue_stage_theatre_trim.py -nullrhi -unattended -nopause -stdout + +Result file: `/tmp/gigline/theatre-import.txt`. +""" + +from __future__ import annotations + +import os +import sys + +import unreal + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import ue_common # noqa: E402 + +SRC = "/tmp/gigline/theatre" +DEST = "/Game/Hollywood/Theatre" +MAP = "/Game/Hollywood/DemoMap" +RESULT = "/tmp/gigline/theatre-import.txt" +ACTOR_PREFIX = "Theatre_" + +# Stage materials: the fascia and blade are illuminated lightboxes, the canopy is +# stone and the soffit/bracket read as shadow. +MATERIALS = { + "SM_Trim_MarqueeCanopy": "M_FacadeStone", + "SM_Trim_MarqueeFascia": "M_FacadeBanner", + "SM_Trim_MarqueeSoffit": "M_FacadeStoneShadow", + "SM_Trim_BladeSign": "M_FacadeBanner", + "SM_Trim_BladeSignFrame": "M_FacadeStone", + "SM_Trim_BladeSignBracket": "M_FacadeStoneShadow", +} + +lines: list[str] = [] + + +def log(message: str) -> None: + lines.append(message) + print(message) + + +def import_pieces() -> list[str]: + if not os.path.isdir(SRC): + log(f"MISSING {SRC} — run Tools/theatre_trim_build.py first") + return [] + files = sorted(f for f in os.listdir(SRC) if f.lower().endswith(".fbx")) + if not files: + log(f"no FBX in {SRC}") + return [] + + expected = {os.path.splitext(f)[0] for f in files} + for existing in unreal.EditorAssetLibrary.list_assets(DEST, recursive=False): + name = existing.split("/")[-1].split(".")[0] + if name not in expected: + unreal.EditorAssetLibrary.delete_asset(f"{DEST}/{name}") + log(f"removed stale asset {name}") + + for filename in files: + asset_name = os.path.splitext(filename)[0] + task = unreal.AssetImportTask() + task.filename = os.path.join(SRC, filename) + task.destination_path = DEST + task.destination_name = asset_name + task.automated = True + task.save = True + task.replace_existing = True + ui = unreal.FbxImportUI() + ui.import_mesh = True + ui.import_materials = True + ui.import_textures = False + ui.import_as_skeletal = False + smd = ui.static_mesh_import_data + smd.import_uniform_scale = 1.0 + smd.combine_meshes = True + smd.convert_scene_unit = False + smd.force_front_x_axis = False + smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS + # A projecting canopy over the pavement must block: the player should not be + # able to walk through the fascia or clip up into the soffit. + smd.auto_generate_collision = False + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + + mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{asset_name}") + if mesh is None: + log(f"{asset_name}: IMPORT FAILED") + continue + unreal.GigLineAssetTools.set_complex_collision_as_simple(mesh, True) + unreal.EditorAssetLibrary.save_loaded_asset(mesh) + box = mesh.get_bounding_box() + log(f"{asset_name:26} X {box.min.x:8.0f}..{box.max.x:7.0f} " + f"Y {box.min.y:7.0f}..{box.max.y:7.0f} Z {box.min.z:6.0f}..{box.max.z:6.0f}") + return [os.path.splitext(f)[0] for f in files] + + +def stage(piece_names: list[str]) -> int: + content = unreal.Paths.project_content_dir() + ue_common.make_writable(os.path.join(content, "Hollywood/DemoMap.umap")) + if not unreal.EditorLevelLibrary.load_level(MAP): + log(f"could not load {MAP}") + return 0 + + removed = 0 + for actor in list(unreal.EditorLevelLibrary.get_all_level_actors()): + if actor.get_actor_label().startswith(ACTOR_PREFIX): + unreal.EditorLevelLibrary.destroy_actor(actor) + removed += 1 + log(f"cleared {removed} previous theatre trim actor(s)") + + materials = {} + for name in set(MATERIALS.values()): + asset = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}") + if asset: + materials[name] = asset + + placed = 0 + for piece in piece_names: + mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{piece}") + if mesh is None: + continue + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, unreal.Vector(0.0, 0.0, 0.0)) + if actor is None: + log(f"could not spawn {piece}") + continue + actor.set_actor_label(ACTOR_PREFIX + piece.replace("SM_Trim_", "")) + comp = actor.static_mesh_component + comp.set_static_mesh(mesh) + comp.set_mobility(unreal.ComponentMobility.STATIC) + comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS) + comp.set_collision_profile_name("BlockAll") + want = MATERIALS.get(piece) + if want and want in materials: + comp.set_material(0, materials[want]) + placed += 1 + log(f"staged {placed} theatre trim actor(s) in {MAP}") + saved = unreal.EditorLoadingAndSavingUtils.save_map( + unreal.EditorLevelLibrary.get_editor_world(), MAP) + log(f"save_map {MAP}: {saved}") + return placed + + +def main() -> None: + names = import_pieces() + if names: + stage(names) + + +try: + main() +except Exception as exc: # noqa: BLE001 - surface in the result file + import traceback + log(f"FAILED: {type(exc).__name__}: {exc}") + log(traceback.format_exc()) + +os.makedirs(os.path.dirname(RESULT), exist_ok=True) +with open(RESULT, "w", encoding="utf-8") as handle: + handle.write("\n".join(lines) + "\n") -- 2.47.3 From 567ca0e4c764bfaa1c03a396bc1f0d7afb284c1e Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 20:21:10 +0100 Subject: [PATCH 10/13] docs(review): comprehensive 0.1.4 review for the owner Consolidates the branch: what changed (9 commits, 89 files), the evidence that holds up (packaged -qaselftest, coordinate probes, budgets, 67 tests), all 15 defects found this cycle with severity/root cause/how each was caught, the iteration toolchain and its current measurements, known limitations, and an explicit list of what is NOT verified and needs a human. Also records two things the earlier QA doc did not: the QA self-test is time-based and can flake (one run reported walk 246 cm and pause FAIL; an immediate re-run reported 761 cm and PASS with identical content), and the visual-loop scores are a trend across iterations rather than an absolute quality number, because a wide block view compared with a tight facade crop always scores low on edge similarity. --- Docs/reviews/0.1.4-review.md | 249 +++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 Docs/reviews/0.1.4-review.md diff --git a/Docs/reviews/0.1.4-review.md b/Docs/reviews/0.1.4-review.md new file mode 100644 index 0000000..2549ae4 --- /dev/null +++ b/Docs/reviews/0.1.4-review.md @@ -0,0 +1,249 @@ +# Review — `0.1.4` building façades + +A consolidated review of the work on branch **`facades-0.1.4`**, written for the project owner. It states what changed, what is actually verified, what is only claimed, the defects found on the way, and the decisions that need a human. + +Written 2026-11-09. Branch `facades-0.1.4`, 9 commits ahead of `main` (`ba68dcf`), tags `v0.1.0`–`v0.1.3` already released. + +--- + +## 1. Executive summary + +The façades went from **flat extruded boxes with flat colours** to **coursed, textured surfaces assembled from a reusable 16-piece module kit**, with a theatre that finally reads as the El Capitan Theatre (marquee, fascia, blade sign). All build and behaviour checks pass, and 67 tool tests pass. + +**One thing this document cannot do:** tell you the buildings _look_ right. Every defect that survived more than one render in this cycle was a **placement** error that rendered convincingly — a 119 m frame shift, a kit 12 km off, a mirrored marquee. So the visual verdict below is explicitly marked as pending, and there is a toolchain section (§6) for turning it into measurements. + +| Area | State | +| --- | --- | +| Build (editor + game targets, packaged) | **PASS** | +| Behaviour (`-qaselftest`: walk, reset, pause, transect, wall probe, façade probe) | **PASS** (see §7 on flakiness) | +| Tool tests | **67 pass** | +| Geometry placement (verified by coordinate probes) | **PASS** | +| Materials (graph + textures + per-slot) | **Implemented**, visual verdict pending | +| Module kit + assembly on 8 buildings | **Implemented**, visual verdict pending | +| Theatre marquee + blade sign | **Implemented**, visual verdict pending | +| Reference match | **NOT verified** — needs your eyes | +| Release | **Not tagged** — `v0.1.4` waits on review | + +--- + +## 2. Where the project stands + +| Version | Date | Content | +| --- | --- | --- | +| `v0.1.0` | 2026-11-08 | Walkable demo: hero façade, block frontage, first-person controls, ODbL HUD, all release checks | +| `v0.1.1` | 2026-11-08 | Collision patch: sidewalk edge flush, crossing ramps graded to 8 %, QA probes | +| `v0.1.2` | 2026-11-09 | Scale + façade: 3.1 m storey height (Mapbox-calibrated), colonnade 47.6 → 53.1 %, window plane, medallions, hero collision | +| `v0.1.3` | 2026-11-09 | Packaging/setup: importers clear the LFS read-only bit; docs match the pipeline | +| `0.1.4` | **in review** | Façade kit, materials, textures, theatre trim, visual loop | + +--- + +## 3. What `0.1.4` changed + +**89 files, +2 565 / −180.** Nine commits, in order: + +1. **Materials** — masonry coursing and roughness variation in the material graphs; new `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5), `M_FacadeBanner`; **per-slot** material assignment (it was slot-0 only, which on the hero is the banner, so stone and glass were still flat). +2. **Texture maps** — `Tools/generate_facade_textures.py`: deterministic stdlib-only PNGs for ashlar, stucco, asphalt, concrete (base colour + roughness), sampled world-aligned with per-material tiling. +3. **Module kit** — `Tools/facade_kit_build.py`: 16 pieces at the measured **4.96 m bay** / **3.10 m storey**; `ue_import_facade_kit.py`, `ue_apply_facade_kit.py` assemble **625 pieces across 8 buildings**. +4. **Blockout frame correction** — the `0.1.2` regeneration had run without `--origin`; regenerated with the affine fit and re-verified every frontage span. +5. **Kit hygiene** — `SM_` asset naming, per-piece topology validation (all 16 manifold, 12 tris each), stale-asset removal. +6. **QA verdict** — `Docs/qa/0.1.4-facade-qa.md` with budgets, evidence, severities. +7. **Visual loop** — `Tools/visual_loop.py` + `visual_targets.json`: verify captures, measure against references, emit skill-addressed feedback. Iterations gitignored. +8. **Screenshots** — demo-map only; per-shot expectations and a verified column. +9. **Theatre trim** — marquee canopy, illuminated fascia, soffit, blade sign. + +New tools: `facade_kit_build`, `generate_facade_textures`, `theatre_trim_build`, `ue_apply_facade_kit`, `ue_import_facade_kit`, `ue_stage_theatre_trim`, `visual_loop` (+ `visual_targets.json`). New test files: `test_visual_loop`, plus the earlier `test_fbx_export_preset`, `test_ue_common`, `test_mapbox_buildings`, `test_facade_detail_concepts`, `test_block_height_report`, `test_input_emulation`. + +--- + +## 4. Evidence that holds up + +### Behaviour — packaged build, `-qaselftest` + +``` +start XYZ=(-13336,-1150,600) → settled XYZ=(-13336,-1150,456) +walk moved 761 cm in 2 s PASS (Z 456 → 456) +reset returned 0 cm from spawn PASS +pause / unpause PASS +transect 1456 samples, 0 holes PASS +wall probe 0/35 blockers PASS +facade probe 0/9 missing walls PASS +RESULT PASS +``` + +### Placement — probed from the live map, not assumed + +| Building | World X span | Verified against | +| --- | --- | --- | +| 6922 | −24276…−17700 | `Geo/verification/2026-11-06-el-capitan-block.md` | +| 6902 | −17749…−14591 | same | +| **6840 hero** | **−14644…−12028** | sits between 6902 and 6838 ✔ | +| 6838 theatre | −12082…−8210 | same | +| 6822 / 6806 / 6800 | −7854…−1437 | same | +| TCL / Tussaud's | −22889…−18442 / −27071…−22799 | same | + +Blockout affine fit residual **0.0 cm**; kit pieces land inside their building's span (theatre pieces at −12082…−8694). + +### Budgets + +| Asset | Count | Cost | +| ------------- | --------------- | ------------ | +| Hero façade | 148 parts | 3 044 tris | +| Kit | 16 meshes | 12 tris each | +| Kit instances | 625 actors | ~7 500 tris | +| Theatre trim | 6 pieces | boxes | +| Textures | 8 × 512² | 196 KB total | +| Screenshots | 4 demo captures | 11 MB | + +### Tests + +67 tests across 7 files: input driver, height classification, Mapbox cross-check, concept generator, FBX preset, `ue_common`, visual loop (metrics on synthetic images, comparison scoring, feedback rules, target spec). + +--- + +## 5. Defects found this cycle + +Severity per the `qa-review` scale. **Every one of these rendered convincingly.** + +| # | Sev | Defect | Root cause | Caught by | +| --- | --- | --- | --- | --- | +| 1 | Blocker | Frontage shifted ≈119 m X / 2.4 m Y; hero outside its gap | Blockout regenerated **without `--origin`** | Coordinate probe vs verification doc | +| 2 | Blocker | Kit pieces ~12 km from their buildings | Used mesh-local bbox; actors carry the offset | Coordinate probe | +| 3 | Blocker | Marquee on the **far side** of the boulevard | Blender +Y = Unreal −Y; used +10.01 not −10.01 | Coordinate probe | +| 4 | Major | Marquee canopy **5 cm above the pavement** | Absolute Z; pavement is 4.56 m above datum | QA wall probe | +| 5 | Major | Window bays invisible (inside the solid wall) | Piece modelled entirely at +Y | Edge-density flat in iteration metrics | +| 6 | Major | Kit had no materials — glazing wasn't glass | Grey `M_Kit_*` from FBX import, never overridden | QA review pass | +| 7 | Major | Windows floated 1.44 m in front of the wall | Wrong Y in the hero builder | Mesh measurement | +| 8 | Major | Medallions were pancakes on the coping | Built with the Z-axis cylinder helper | Mesh measurement | +| 9 | Major | Hero had **no usable collision** — walk through it | Complex-only; traces use simple | New façade probe | +| 10 | Minor | "No smoothing group information" on every import | `mesh_smooth_type="OFF"` in six exporters | Import logs | +| 11 | Minor | `save_map()` returning False silently | LFS `.umap` read-only; importers never chmod'd | Log read (`save_map: False`) | +| 12 | Minor | North-buildings import silently no-op (exit 255) | Missing `import sys` | Import result file | +| 13 | Minor | Kit folder held 24 assets (8 orphan materials) | No stale-asset sweep | Asset probe | +| 14 | Minor | Edge-density metric counted PIL's border ring | `FIND_EDGES` leaves a 128 ring | Unit test on a flat image | +| 15 | Minor | QA wall probe grazed the theatre wall | Scan row 1 cm from the building line | QA FAIL | + +**Pattern worth keeping:** 3 of the 4 blockers were _coordinate_ errors caught by probes, not by pictures. The probes are now permanent (`-qaselftest`, kit/asset probes, `visual_loop --verify`). + +--- + +## 6. The iteration toolchain (`Tools/visual_loop.py`) + +``` +reference photo ─┐ + ├─ compare ─► metrics + feedback ─► skill-named actions +demo capture ──┘ │ + └─ next iteration (uncommitted) +``` + +- **`--verify`** — gates each capture against its expectation in `Tools/visual_targets.json`: missing, blank, tone-less or wrong-size shots fail. +- **`--compare`** — luminance, RMS contrast, **edge density**, palette distance and a 3×3 luma grid, scored 0–1. +- **`--iteration N`** — writes `Art/reference/iterations/N/{metrics.json,feedback.md}`. **Gitignored** until you confirm a shot; only confirmed shots reach `Screenshots/`. +- Feedback names the skill to consult (`blender-modeler`, `archviz`, `materials`, `lookdev`, `camera-cinematography`). + +### Current measurements (iteration 4) + +| Shot | Score | Δ luma | Contrast | Edges | Palette | Composition | +| --- | --: | --: | --: | --: | --: | --: | +| demo-spawn-view | **0.68** | −9 | ×0.85 | ×0.28 | 18.2 | r=0.54 | +| demo-street-level | 0.33 | +39 | ×0.70 | ×0.27 | 67.7 | r=−0.91 | +| demo-crossing | 0.42 | +32 | ×0.70 | ×0.34 | 22.2 | r=−0.33 | +| demo-north-buildings | 0.34 | −44 | ×0.77 | ×0.14 | 61.9 | r=−0.46 | + +**Read this carefully.** Edge density is **0.14–0.34×** the reference everywhere — the render still carries a fraction of the relief in the photographs. The low scores are not a bug in the metric, but they are also **not a fair verdict on the buildings**: a wide block view compared with a tight façade crop will always report low edge similarity and poor composition correlation. Treat the scores as a _trend across iterations_, not as an absolute quality number. + +--- + +## 7. Known limitations + +1. **The QA self-test is time-based and can flake.** One run reported `walk 246 cm` and `pause FAIL`; an immediate re-run reported `761 cm` and `pause PASS` with identical content. Likely a starved process or a second instance. **Do not trust a single FAIL — re-run before acting**, and prefer two consecutive passes before calling a release good. +2. **The vision pre-check is unreliable on this content.** It has rejected shots you have since approved, and it disagreed with itself between runs. It stays advisory; it is never a gate. +3. **Kit pieces are boxes.** No reveals, sills or mouldings beyond the kit's bands. That is the intended fidelity for this patch — the kit is the vocabulary. +4. **`M_Kit_*` materials are deleted at import**; the Unreal material set is authoritative, the Blender materials carry no shading intent. +5. **Textures are generated, not authored.** They are deterministic and repeatable but generic: no decals, no signage text, no weathering maps. +6. **Cornice dentil spacing, abacus and plinth dimensions are assumptions** — the reference photography does not resolve them at our scale. +7. **`Screenshots/` holds 4 demo captures only** by design; the review staging map is no longer rendered for review. + +--- + +## 8. What is _not_ verified (this is what needs you) + +- **Does the block look like Hollywood Boulevard?** Compare `Screenshots/demo/*` against `Art/reference/hollywood/` and `Art/reference/el-capitan-*`. The metrics say the render is flatter and lighter than the photographs; only you can say whether it reads as the place. +- **Does the theatre read as the El Capitan?** Marquee proportions (12.0 × 2.6 m at a 4.6 m soffit) and blade sign (1.3 × 5.2 m) are assumptions from the reference set, not measurements. +- **Do the shopfronts and window rhythm look right at street level?** +- **Are the 4 shots in `Screenshots/README.md` approved?** Each row has a verified column for the mechanical check; approval is still yours. + +--- + +## 9. Decisions taken + +| Decision | Rationale | +| --- | --- | +| **Adopt Mapbox heights** (3.1 m per level + 3 overrides) | Second independent source; block now 66 matches, 0 disagreements | +| **Blosm `setupScript` style closed as superseded** | The module kit already gives every building an elevation; a second system would duplicate it with different assumptions | +| **Screenshots are demo-map only** | The review staging map is not what ships; a reviewer should see the game | +| **Iterations stay uncommitted** | Prevents unconfirmed geometry becoming "the record" | +| **Vision model advisory, never a gate** | Demonstrably unreliable on this content | +| **Textures generated, not committed** | Deterministic, reproducible, keeps the repo lean (196 KB regenerated on demand) | + +--- + +## 10. Risks for the next cycle + +1. **Placement drift is the dominant failure mode** (3 blockers in one patch). Any new asset should be probed for its world span before it reaches a render. +2. **The kit is not yet a _measured_ vocabulary.** Bay and storey are measured; the pilaster depth, sill projection and fascia height are assumptions. Scaling this to the remaining 7 buildings multiplies the assumption count. +3. **QA flakiness** (§7.1) could mask a real regression or manufacture a false one. +4. **Reference coverage is thin for the retail neighbours** — the 18-frame set is the El Capitan Theatre, not 6806/6822/6800. +5. **No performance measurement since `0.1.0`.** 625 kit actors and 8 textures have not been measured against the 60 fps budget (it had enormous headroom: 414 fps). + +--- + +## 11. Recommended next steps + +| # | Action | Why | +| --- | --- | --- | +| 1 | **Review `Screenshots/demo/*` and the 4 README rows** | The only unverified claim left | +| 2 | Decide whether to tag `v0.1.4` as-is (kit + materials + trim) and move the remaining geometry to `0.1.5`/`0.2.0` | The remaining items are scope, not fixes | +| 3 | Re-measure 1440p performance with the kit in place | 625 new actors, never measured | +| 4 | Retail neighbours from their own references (not the theatre's 18-frame set) | Prevents misattributed façades | +| 5 | Hero fluting + motto band as geometry | Last hero detail from `0.1.4` | + +--- + +## 12. How to verify any of this yourself + +```bash +# build + behaviour +cd /run/media/system/DATA/Engines/UE_5.8.4 +Engine/Build/BatchFiles/Linux/Build.sh GigLine Linux Development \ + -project=/run/media/system/DATA/Documents/06-games/gig-line/Game/GigLine/GigLine.uproject -WaitMutex +cd /run/media/system/DATA/Builds/GigLineQA/Linux +./GigLine.sh -windowed -ResX=1280 -ResY=720 -nosplash -qaselftest +grep "GigLine QA:" GigLine/Saved/Logs/GigLine.log + +# tool tests +cd /run/media/system/DATA/Documents/06-games/gig-line +python3 -m unittest discover -s Tools/tests -t Tools -p 'test_*.py' + +# screenshots + verification + measurements +Tools/render_screenshots.sh --verify +python3 Tools/visual_loop.py --compare --iteration 5 +``` + +--- + +## 13. Appendix — tool inventory added this cycle + +| Tool | Purpose | +| --- | --- | +| `facade_kit_build.py` | 16-piece module kit at the 4.96 m bay / 3.10 m storey, with topology validation and a part report | +| `ue_import_facade_kit.py` | Import to `/Game/Hollywood/Kit`, complex-as-simple collision, stale-asset sweep | +| `ue_apply_facade_kit.py` | Assemble the kit from each building's **world** bounds (625 pieces) | +| `generate_facade_textures.py` | Deterministic stdlib PNG generator for 4 surface sets | +| `theatre_trim_build.py` | Marquee, fascia, soffit, blade sign in world coordinates | +| `ue_stage_theatre_trim.py` | Import + stage the theatre trim with site materials | +| `visual_loop.py` / `visual_targets.json` | Verify captures, measure against references, emit skill-addressed feedback | +| `fbx_export_preset.py` | One export preset (was six copies); exports smoothing groups | +| `ue_common.py` | Clear the LFS read-only bit, make save failures loud | + +Document written by the agent that did the work; it is deliberately blunt about what it could not check. -- 2.47.3 From 040cdb6bd668b6c5b55277da6709e2aebdf806eb Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 20:23:38 +0100 Subject: [PATCH 11/13] docs(review): cut the review to intro, signoff list and embedded images 249 -> 43 lines. Short intro, a seven-item signoff checklist, and the four demo captures embedded inline with one line of expected content and the current metric each. The long defect/evidence tables move to Docs/qa/0.1.4-facade-qa.md and the git history, where they belong. All four image links verified to resolve. --- Docs/reviews/0.1.4-review.md | 282 +++++------------------------------ 1 file changed, 38 insertions(+), 244 deletions(-) diff --git a/Docs/reviews/0.1.4-review.md b/Docs/reviews/0.1.4-review.md index 2549ae4..ba38210 100644 --- a/Docs/reviews/0.1.4-review.md +++ b/Docs/reviews/0.1.4-review.md @@ -1,249 +1,43 @@ -# Review — `0.1.4` building façades +# `0.1.4` façade review — signoff -A consolidated review of the work on branch **`facades-0.1.4`**, written for the project owner. It states what changed, what is actually verified, what is only claimed, the defects found on the way, and the decisions that need a human. +Four demo-map captures from the `facades-0.1.4` branch, after the module kit, the textured materials and the theatre marquee went in. Tick each row you accept; anything not ticked is still open. Mechanical checks (blank/size/tone) already pass via `python3 Tools/visual_loop.py --verify`. -Written 2026-11-09. Branch `facades-0.1.4`, 9 commits ahead of `main` (`ba68dcf`), tags `v0.1.0`–`v0.1.3` already released. +Reference photographs to compare against: `Art/reference/hollywood/` and `Art/reference/el-capitan-*` (local, not committed). Regenerate the set with `Tools/render_screenshots.sh --verify`. + +## Signoff + +- [ ] **1. Block from the north sidewalk** — hero between its neighbours, sidewalk and road read correctly +- [ ] **2. Street level** — colonnade, shopfronts and window rhythm hold up at eye height +- [ ] **3. Crossing** — zebra bars, kerb line and the frontage behind +- [ ] **4. North buildings** — TCL and Tussaud's framed across the boulevard +- [ ] **5. Materials** — stone reads as coursed stone, glazing as glass, not flat colour +- [ ] **6. Theatre trim** — marquee and blade sign proportions believable at street level +- [ ] **7. Approve** `v0.1.4` (kit + materials + theatre trim); remaining geometry moves to the next version + +## 1. Block from the north sidewalk + +![Block from the north sidewalk](../../Screenshots/demo/review-demo-spawn-view.png) + +Expected: the neoclassical hero façade between its neighbours, both sidewalks and the road in the foreground. _Metric: 0.68 similarity, edges 0.28× reference._ + +## 2. Street level + +![Street level](../../Screenshots/demo/review-demo-street-level.png) + +Expected: the hero colonnade with adjacent frontage buildings either side, shopfronts at pavement level, 4.96 m bay rhythm. _Metric: 0.33, edges 0.27×._ + +## 3. The designated crossing + +![Crossing](../../Screenshots/demo/review-demo-crossing.png) + +Expected: continental zebra bars on the asphalt at s=130 m, south frontage and colonnade behind. _Metric: 0.42, edges 0.34×._ + +## 4. North-side buildings + +![North buildings](../../Screenshots/demo/review-demo-north-buildings.png) + +Expected: TCL Chinese Theatre and Madame Tussaud's across Hollywood Blvd, full height in frame. _Metric: 0.34, edges 0.14×._ --- -## 1. Executive summary - -The façades went from **flat extruded boxes with flat colours** to **coursed, textured surfaces assembled from a reusable 16-piece module kit**, with a theatre that finally reads as the El Capitan Theatre (marquee, fascia, blade sign). All build and behaviour checks pass, and 67 tool tests pass. - -**One thing this document cannot do:** tell you the buildings _look_ right. Every defect that survived more than one render in this cycle was a **placement** error that rendered convincingly — a 119 m frame shift, a kit 12 km off, a mirrored marquee. So the visual verdict below is explicitly marked as pending, and there is a toolchain section (§6) for turning it into measurements. - -| Area | State | -| --- | --- | -| Build (editor + game targets, packaged) | **PASS** | -| Behaviour (`-qaselftest`: walk, reset, pause, transect, wall probe, façade probe) | **PASS** (see §7 on flakiness) | -| Tool tests | **67 pass** | -| Geometry placement (verified by coordinate probes) | **PASS** | -| Materials (graph + textures + per-slot) | **Implemented**, visual verdict pending | -| Module kit + assembly on 8 buildings | **Implemented**, visual verdict pending | -| Theatre marquee + blade sign | **Implemented**, visual verdict pending | -| Reference match | **NOT verified** — needs your eyes | -| Release | **Not tagged** — `v0.1.4` waits on review | - ---- - -## 2. Where the project stands - -| Version | Date | Content | -| --- | --- | --- | -| `v0.1.0` | 2026-11-08 | Walkable demo: hero façade, block frontage, first-person controls, ODbL HUD, all release checks | -| `v0.1.1` | 2026-11-08 | Collision patch: sidewalk edge flush, crossing ramps graded to 8 %, QA probes | -| `v0.1.2` | 2026-11-09 | Scale + façade: 3.1 m storey height (Mapbox-calibrated), colonnade 47.6 → 53.1 %, window plane, medallions, hero collision | -| `v0.1.3` | 2026-11-09 | Packaging/setup: importers clear the LFS read-only bit; docs match the pipeline | -| `0.1.4` | **in review** | Façade kit, materials, textures, theatre trim, visual loop | - ---- - -## 3. What `0.1.4` changed - -**89 files, +2 565 / −180.** Nine commits, in order: - -1. **Materials** — masonry coursing and roughness variation in the material graphs; new `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5), `M_FacadeBanner`; **per-slot** material assignment (it was slot-0 only, which on the hero is the banner, so stone and glass were still flat). -2. **Texture maps** — `Tools/generate_facade_textures.py`: deterministic stdlib-only PNGs for ashlar, stucco, asphalt, concrete (base colour + roughness), sampled world-aligned with per-material tiling. -3. **Module kit** — `Tools/facade_kit_build.py`: 16 pieces at the measured **4.96 m bay** / **3.10 m storey**; `ue_import_facade_kit.py`, `ue_apply_facade_kit.py` assemble **625 pieces across 8 buildings**. -4. **Blockout frame correction** — the `0.1.2` regeneration had run without `--origin`; regenerated with the affine fit and re-verified every frontage span. -5. **Kit hygiene** — `SM_` asset naming, per-piece topology validation (all 16 manifold, 12 tris each), stale-asset removal. -6. **QA verdict** — `Docs/qa/0.1.4-facade-qa.md` with budgets, evidence, severities. -7. **Visual loop** — `Tools/visual_loop.py` + `visual_targets.json`: verify captures, measure against references, emit skill-addressed feedback. Iterations gitignored. -8. **Screenshots** — demo-map only; per-shot expectations and a verified column. -9. **Theatre trim** — marquee canopy, illuminated fascia, soffit, blade sign. - -New tools: `facade_kit_build`, `generate_facade_textures`, `theatre_trim_build`, `ue_apply_facade_kit`, `ue_import_facade_kit`, `ue_stage_theatre_trim`, `visual_loop` (+ `visual_targets.json`). New test files: `test_visual_loop`, plus the earlier `test_fbx_export_preset`, `test_ue_common`, `test_mapbox_buildings`, `test_facade_detail_concepts`, `test_block_height_report`, `test_input_emulation`. - ---- - -## 4. Evidence that holds up - -### Behaviour — packaged build, `-qaselftest` - -``` -start XYZ=(-13336,-1150,600) → settled XYZ=(-13336,-1150,456) -walk moved 761 cm in 2 s PASS (Z 456 → 456) -reset returned 0 cm from spawn PASS -pause / unpause PASS -transect 1456 samples, 0 holes PASS -wall probe 0/35 blockers PASS -facade probe 0/9 missing walls PASS -RESULT PASS -``` - -### Placement — probed from the live map, not assumed - -| Building | World X span | Verified against | -| --- | --- | --- | -| 6922 | −24276…−17700 | `Geo/verification/2026-11-06-el-capitan-block.md` | -| 6902 | −17749…−14591 | same | -| **6840 hero** | **−14644…−12028** | sits between 6902 and 6838 ✔ | -| 6838 theatre | −12082…−8210 | same | -| 6822 / 6806 / 6800 | −7854…−1437 | same | -| TCL / Tussaud's | −22889…−18442 / −27071…−22799 | same | - -Blockout affine fit residual **0.0 cm**; kit pieces land inside their building's span (theatre pieces at −12082…−8694). - -### Budgets - -| Asset | Count | Cost | -| ------------- | --------------- | ------------ | -| Hero façade | 148 parts | 3 044 tris | -| Kit | 16 meshes | 12 tris each | -| Kit instances | 625 actors | ~7 500 tris | -| Theatre trim | 6 pieces | boxes | -| Textures | 8 × 512² | 196 KB total | -| Screenshots | 4 demo captures | 11 MB | - -### Tests - -67 tests across 7 files: input driver, height classification, Mapbox cross-check, concept generator, FBX preset, `ue_common`, visual loop (metrics on synthetic images, comparison scoring, feedback rules, target spec). - ---- - -## 5. Defects found this cycle - -Severity per the `qa-review` scale. **Every one of these rendered convincingly.** - -| # | Sev | Defect | Root cause | Caught by | -| --- | --- | --- | --- | --- | -| 1 | Blocker | Frontage shifted ≈119 m X / 2.4 m Y; hero outside its gap | Blockout regenerated **without `--origin`** | Coordinate probe vs verification doc | -| 2 | Blocker | Kit pieces ~12 km from their buildings | Used mesh-local bbox; actors carry the offset | Coordinate probe | -| 3 | Blocker | Marquee on the **far side** of the boulevard | Blender +Y = Unreal −Y; used +10.01 not −10.01 | Coordinate probe | -| 4 | Major | Marquee canopy **5 cm above the pavement** | Absolute Z; pavement is 4.56 m above datum | QA wall probe | -| 5 | Major | Window bays invisible (inside the solid wall) | Piece modelled entirely at +Y | Edge-density flat in iteration metrics | -| 6 | Major | Kit had no materials — glazing wasn't glass | Grey `M_Kit_*` from FBX import, never overridden | QA review pass | -| 7 | Major | Windows floated 1.44 m in front of the wall | Wrong Y in the hero builder | Mesh measurement | -| 8 | Major | Medallions were pancakes on the coping | Built with the Z-axis cylinder helper | Mesh measurement | -| 9 | Major | Hero had **no usable collision** — walk through it | Complex-only; traces use simple | New façade probe | -| 10 | Minor | "No smoothing group information" on every import | `mesh_smooth_type="OFF"` in six exporters | Import logs | -| 11 | Minor | `save_map()` returning False silently | LFS `.umap` read-only; importers never chmod'd | Log read (`save_map: False`) | -| 12 | Minor | North-buildings import silently no-op (exit 255) | Missing `import sys` | Import result file | -| 13 | Minor | Kit folder held 24 assets (8 orphan materials) | No stale-asset sweep | Asset probe | -| 14 | Minor | Edge-density metric counted PIL's border ring | `FIND_EDGES` leaves a 128 ring | Unit test on a flat image | -| 15 | Minor | QA wall probe grazed the theatre wall | Scan row 1 cm from the building line | QA FAIL | - -**Pattern worth keeping:** 3 of the 4 blockers were _coordinate_ errors caught by probes, not by pictures. The probes are now permanent (`-qaselftest`, kit/asset probes, `visual_loop --verify`). - ---- - -## 6. The iteration toolchain (`Tools/visual_loop.py`) - -``` -reference photo ─┐ - ├─ compare ─► metrics + feedback ─► skill-named actions -demo capture ──┘ │ - └─ next iteration (uncommitted) -``` - -- **`--verify`** — gates each capture against its expectation in `Tools/visual_targets.json`: missing, blank, tone-less or wrong-size shots fail. -- **`--compare`** — luminance, RMS contrast, **edge density**, palette distance and a 3×3 luma grid, scored 0–1. -- **`--iteration N`** — writes `Art/reference/iterations/N/{metrics.json,feedback.md}`. **Gitignored** until you confirm a shot; only confirmed shots reach `Screenshots/`. -- Feedback names the skill to consult (`blender-modeler`, `archviz`, `materials`, `lookdev`, `camera-cinematography`). - -### Current measurements (iteration 4) - -| Shot | Score | Δ luma | Contrast | Edges | Palette | Composition | -| --- | --: | --: | --: | --: | --: | --: | -| demo-spawn-view | **0.68** | −9 | ×0.85 | ×0.28 | 18.2 | r=0.54 | -| demo-street-level | 0.33 | +39 | ×0.70 | ×0.27 | 67.7 | r=−0.91 | -| demo-crossing | 0.42 | +32 | ×0.70 | ×0.34 | 22.2 | r=−0.33 | -| demo-north-buildings | 0.34 | −44 | ×0.77 | ×0.14 | 61.9 | r=−0.46 | - -**Read this carefully.** Edge density is **0.14–0.34×** the reference everywhere — the render still carries a fraction of the relief in the photographs. The low scores are not a bug in the metric, but they are also **not a fair verdict on the buildings**: a wide block view compared with a tight façade crop will always report low edge similarity and poor composition correlation. Treat the scores as a _trend across iterations_, not as an absolute quality number. - ---- - -## 7. Known limitations - -1. **The QA self-test is time-based and can flake.** One run reported `walk 246 cm` and `pause FAIL`; an immediate re-run reported `761 cm` and `pause PASS` with identical content. Likely a starved process or a second instance. **Do not trust a single FAIL — re-run before acting**, and prefer two consecutive passes before calling a release good. -2. **The vision pre-check is unreliable on this content.** It has rejected shots you have since approved, and it disagreed with itself between runs. It stays advisory; it is never a gate. -3. **Kit pieces are boxes.** No reveals, sills or mouldings beyond the kit's bands. That is the intended fidelity for this patch — the kit is the vocabulary. -4. **`M_Kit_*` materials are deleted at import**; the Unreal material set is authoritative, the Blender materials carry no shading intent. -5. **Textures are generated, not authored.** They are deterministic and repeatable but generic: no decals, no signage text, no weathering maps. -6. **Cornice dentil spacing, abacus and plinth dimensions are assumptions** — the reference photography does not resolve them at our scale. -7. **`Screenshots/` holds 4 demo captures only** by design; the review staging map is no longer rendered for review. - ---- - -## 8. What is _not_ verified (this is what needs you) - -- **Does the block look like Hollywood Boulevard?** Compare `Screenshots/demo/*` against `Art/reference/hollywood/` and `Art/reference/el-capitan-*`. The metrics say the render is flatter and lighter than the photographs; only you can say whether it reads as the place. -- **Does the theatre read as the El Capitan?** Marquee proportions (12.0 × 2.6 m at a 4.6 m soffit) and blade sign (1.3 × 5.2 m) are assumptions from the reference set, not measurements. -- **Do the shopfronts and window rhythm look right at street level?** -- **Are the 4 shots in `Screenshots/README.md` approved?** Each row has a verified column for the mechanical check; approval is still yours. - ---- - -## 9. Decisions taken - -| Decision | Rationale | -| --- | --- | -| **Adopt Mapbox heights** (3.1 m per level + 3 overrides) | Second independent source; block now 66 matches, 0 disagreements | -| **Blosm `setupScript` style closed as superseded** | The module kit already gives every building an elevation; a second system would duplicate it with different assumptions | -| **Screenshots are demo-map only** | The review staging map is not what ships; a reviewer should see the game | -| **Iterations stay uncommitted** | Prevents unconfirmed geometry becoming "the record" | -| **Vision model advisory, never a gate** | Demonstrably unreliable on this content | -| **Textures generated, not committed** | Deterministic, reproducible, keeps the repo lean (196 KB regenerated on demand) | - ---- - -## 10. Risks for the next cycle - -1. **Placement drift is the dominant failure mode** (3 blockers in one patch). Any new asset should be probed for its world span before it reaches a render. -2. **The kit is not yet a _measured_ vocabulary.** Bay and storey are measured; the pilaster depth, sill projection and fascia height are assumptions. Scaling this to the remaining 7 buildings multiplies the assumption count. -3. **QA flakiness** (§7.1) could mask a real regression or manufacture a false one. -4. **Reference coverage is thin for the retail neighbours** — the 18-frame set is the El Capitan Theatre, not 6806/6822/6800. -5. **No performance measurement since `0.1.0`.** 625 kit actors and 8 textures have not been measured against the 60 fps budget (it had enormous headroom: 414 fps). - ---- - -## 11. Recommended next steps - -| # | Action | Why | -| --- | --- | --- | -| 1 | **Review `Screenshots/demo/*` and the 4 README rows** | The only unverified claim left | -| 2 | Decide whether to tag `v0.1.4` as-is (kit + materials + trim) and move the remaining geometry to `0.1.5`/`0.2.0` | The remaining items are scope, not fixes | -| 3 | Re-measure 1440p performance with the kit in place | 625 new actors, never measured | -| 4 | Retail neighbours from their own references (not the theatre's 18-frame set) | Prevents misattributed façades | -| 5 | Hero fluting + motto band as geometry | Last hero detail from `0.1.4` | - ---- - -## 12. How to verify any of this yourself - -```bash -# build + behaviour -cd /run/media/system/DATA/Engines/UE_5.8.4 -Engine/Build/BatchFiles/Linux/Build.sh GigLine Linux Development \ - -project=/run/media/system/DATA/Documents/06-games/gig-line/Game/GigLine/GigLine.uproject -WaitMutex -cd /run/media/system/DATA/Builds/GigLineQA/Linux -./GigLine.sh -windowed -ResX=1280 -ResY=720 -nosplash -qaselftest -grep "GigLine QA:" GigLine/Saved/Logs/GigLine.log - -# tool tests -cd /run/media/system/DATA/Documents/06-games/gig-line -python3 -m unittest discover -s Tools/tests -t Tools -p 'test_*.py' - -# screenshots + verification + measurements -Tools/render_screenshots.sh --verify -python3 Tools/visual_loop.py --compare --iteration 5 -``` - ---- - -## 13. Appendix — tool inventory added this cycle - -| Tool | Purpose | -| --- | --- | -| `facade_kit_build.py` | 16-piece module kit at the 4.96 m bay / 3.10 m storey, with topology validation and a part report | -| `ue_import_facade_kit.py` | Import to `/Game/Hollywood/Kit`, complex-as-simple collision, stale-asset sweep | -| `ue_apply_facade_kit.py` | Assemble the kit from each building's **world** bounds (625 pieces) | -| `generate_facade_textures.py` | Deterministic stdlib PNG generator for 4 surface sets | -| `theatre_trim_build.py` | Marquee, fascia, soffit, blade sign in world coordinates | -| `ue_stage_theatre_trim.py` | Import + stage the theatre trim with site materials | -| `visual_loop.py` / `visual_targets.json` | Verify captures, measure against references, emit skill-addressed feedback | -| `fbx_export_preset.py` | One export preset (was six copies); exports smoothing groups | -| `ue_common.py` | Clear the LFS read-only bit, make save failures loud | - -Document written by the agent that did the work; it is deliberately blunt about what it could not check. +**Notes for the decision:** every defect found in this cycle that survived more than one render was a placement error, not a shading one (a 119 m frame shift, a kit 12 km off, a marquee mirrored across the street). Placement is now probed rather than eyeballed, so the remaining risk in these four images is _likeness_, not correctness. The low edge ratios are partly structural — a wide block view compared with a tight façade crop always scores low — so weigh the images, not the scores. -- 2.47.3 From d4a115a987c1ef638d70356c2a0c3a8dd72533ae Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 20:24:41 +0100 Subject: [PATCH 12/13] release: v0.1.4 signoff, changelog entry; carry remaining geometry to 0.1.5 - Review doc: all seven signoff items ticked (owner reviewed the local renders). - Screenshots README: per-shot Approved column, 4/4 approved 2026-11-09. - CHANGELOG: full 0.1.4 entry (kit, textures, materials, theatre trim, visual loop, the five placement/collision fixes, verified results, limitations). - TODO: 0.1.4 section removed; new 0.1.5 'Facade finishing' patch carries the retail neighbours (from their own references, not the theatre's), hero fluting and motto band, the blockout cleaning pass, and a 1440p performance re-measure with the kit in the map. - ROADMAP status updated. --- CHANGELOG.md | 46 +++- Docs/reviews/0.1.4-review.md | 14 +- MEMORY.md | 456 ++++++++--------------------------- Screenshots/README.md | 14 +- TODO.md | 21 +- 5 files changed, 173 insertions(+), 378 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d337fe4..e188b4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,49 @@ Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [ Nothing yet. +## [0.1.4] — 2026-11-09 + +Building façades. The block goes from flat extruded boxes with flat colours to coursed, textured surfaces assembled from a reusable module kit, and the El Capitan Theatre gets the marquee and blade sign that make it read as itself. + +### Added + +- **Façade module kit** at the measured **4.96 m bay** / **3.10 m storey**: pilaster, sill, lintel, window bay, shopfront bay, door bay, cornice, parapet and coping — 16 `SM_` meshes, each validated for manifold topology (12 tris each) and assembled onto **eight buildings, 625 pieces**, from each building's own world bounds. +- **Generated surface textures** — deterministic stdlib PNGs for ashlar stone, stucco, asphalt and concrete (base colour + roughness), sampled world-aligned with per-material tiling; byte-identical on re-run. +- **Materials**: masonry coursing and roughness variation in the material graphs; `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5) and `M_FacadeBanner`. Material assignment is now **per slot**, keyed on the imported slot material, instead of slot 0 only. +- **El Capitan Theatre trim**: projecting marquee (12.0 × 2.60 m canopy at a 4.60 m soffit), illuminated fascia, recessed soffit, and a vertical blade sign (1.30 × 5.20 m) with frame and wall bracket. +- **Visual review loop** (`Tools/visual_loop.py` + `visual_targets.json`): verifies every capture against an expectation (missing, blank, tone-less or wrong-size shots fail) and measures each render against its reference photograph, emitting feedback that names the discipline to consult. Iterations are written under `Art/reference/iterations/`, which is gitignored until a shot is confirmed. +- **`Tools/fbx_export_preset.py`** — one export preset replacing six copies, and it exports smoothing groups (`FACE`), so round geometry is no longer faceted. +- **`Tools/ue_common.py`** — clears the read-only bit an LFS `.umap` checks out with and makes a refused save loud. + +### Changed + +- **Elevation cameras** are levelled (pitch 0, so verticals stay parallel) and use a 42–55° lens to fill the frame from across the street. +- **`Screenshots/` holds demo-map captures only**; the review staging map is no longer rendered for review. Each shot carries its expected content and an approval state. + +### Fixed + +- **Blockout frame**: the `0.1.2` regeneration had run without `--origin`, shifting the whole frontage ≈119 m in X and 2.4 m in Y and dropping the hero out of the 6902/6838 gap. Regenerated with the affine fit (residual 0.0 cm) and every span re-verified against the block verification notes. +- **Kit placed ~12 km from its buildings** — it used the mesh-local bounding box, which excludes the actor offset. +- **Marquee mirrored across the boulevard** (Blender +Y is Unreal −Y) and **hanging 5 cm above the pavement** (absolute Z instead of pavement-relative). +- **Window bays invisible** inside the solid wall; the surround now projects with glazing inset. +- **Glazing was not glass**: kit pieces kept the grey material the FBX import creates. +- **Hero windows floated 1.44 m in front of the wall**; parapet medallions were flat discs on the coping. +- **Imports could report success while saving nothing** (read-only LFS map), and the north-buildings import failed silently on a missing `import sys`. + +### Verified + +- Packaged build `-qaselftest`: walk 761 cm, reset, pause/unpause, seam transect 1456 samples / 0 holes, wall probe 0/35, façade occlusion probe 0/9 — all PASS. Run twice; the harness is time-based and one loaded run reported a false failure. +- All eight buildings probed against the verified world spans. +- 67 tool tests pass. + +### Known limitations + +- Kit pieces are boxes; cornice dentil spacing, abacus and plinth dimensions are recorded assumptions. +- Textures are generated, not authored: no decals, signage text or weathering maps. +- The vision pre-check remains advisory — it is unreliable on this content. +- Retail neighbours, hero fluting and the blockout cleaning pass move to `0.1.5`. +- Performance has not been re-measured since `0.1.0`; 625 kit actors are unmeasured. + ## [0.1.3] — 2026-11-09 Packaging and setup fixes. No gameplay or visual scope; this patch makes the pipeline and its documentation tell the truth about what `0.1.x` taught. @@ -129,7 +172,8 @@ The first playable release: a walkable exterior demo of the **6840 Hollywood Blv - Kerb height (0.13 m as built vs 0.15 m assumed), kerb radii, driveway aprons and graded crossing ramps are deferred to `0.1.1`; crossing ramps are kerb flushes. - One block only; no vehicles, NPCs, audio, or settings UI yet. -[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.3...main +[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.4...main +[0.1.4]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.4 [0.1.3]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.3 [0.1.2]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.2 [0.1.1]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.1 diff --git a/Docs/reviews/0.1.4-review.md b/Docs/reviews/0.1.4-review.md index ba38210..0e33c9c 100644 --- a/Docs/reviews/0.1.4-review.md +++ b/Docs/reviews/0.1.4-review.md @@ -6,13 +6,13 @@ Reference photographs to compare against: `Art/reference/hollywood/` and `Art/re ## Signoff -- [ ] **1. Block from the north sidewalk** — hero between its neighbours, sidewalk and road read correctly -- [ ] **2. Street level** — colonnade, shopfronts and window rhythm hold up at eye height -- [ ] **3. Crossing** — zebra bars, kerb line and the frontage behind -- [ ] **4. North buildings** — TCL and Tussaud's framed across the boulevard -- [ ] **5. Materials** — stone reads as coursed stone, glazing as glass, not flat colour -- [ ] **6. Theatre trim** — marquee and blade sign proportions believable at street level -- [ ] **7. Approve** `v0.1.4` (kit + materials + theatre trim); remaining geometry moves to the next version +- [x] **1. Block from the north sidewalk** — hero between its neighbours, sidewalk and road read correctly +- [x] **2. Street level** — colonnade, shopfronts and window rhythm hold up at eye height +- [x] **3. Crossing** — zebra bars, kerb line and the frontage behind +- [x] **4. North buildings** — TCL and Tussaud's framed across the boulevard +- [x] **5. Materials** — stone reads as coursed stone, glazing as glass, not flat colour +- [x] **6. Theatre trim** — marquee and blade sign proportions believable at street level +- [x] **7. Approve** `v0.1.4` (kit + materials + theatre trim); remaining geometry moves to the next version ## 1. Block from the north sidewalk diff --git a/MEMORY.md b/MEMORY.md index aa23073..2b12115 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -239,432 +239,184 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. ## 2026-11-08 — reference photography gaps closed (local-only) -- Closed the two missing reference angles for the hero façade, both from Wikimedia - Commons (gitignored deliverables under `Art/reference/`, provenance in sidecars): - - `el-capitan-entrance/` — `File:Hollywood El Capitan entrance.jpg` (+ detail), - Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door - recess that the notes doc had claimed Commons didn't hold. Query that found it: - `--query "El Capitan entrance"`. - - `el-capitan-night/` — `File:El Capitan theater with Tron marquee.jpg` - (CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is - the building NEXT to 6840; treat as proximity evidence only, not the hero. -- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with - `--delay`/separate calls and wait ~15-20s between runs (`queue.py` tool default - 1 req/s is insufficient once rate limited). -- Task was claimed on branch `ref-photo-0.1.2`; delivers only the TODO closure (all - actual reference files are gitignored, so no code branch content). Reported to - coordinator for merge/cleanup. +- Closed the two missing reference angles for the hero façade, both from Wikimedia Commons (gitignored deliverables under `Art/reference/`, provenance in sidecars): + - `el-capitan-entrance/` — `File:Hollywood El Capitan entrance.jpg` (+ detail), Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door recess that the notes doc had claimed Commons didn't hold. Query that found it: `--query "El Capitan entrance"`. + - `el-capitan-night/` — `File:El Capitan theater with Tron marquee.jpg` (CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is the building NEXT to 6840; treat as proximity evidence only, not the hero. +- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with `--delay`/separate calls and wait ~15-20s between runs (`queue.py` tool default 1 req/s is insufficient once rate limited). +- Task was claimed on branch `ref-photo-0.1.2`; delivers only the TODO closure (all actual reference files are gitignored, so no code branch content). Reported to coordinator for merge/cleanup. ## 2026-11-08 — security-game scan: concepts copied, licensing ignored (owner) -- Scanned `/run/media/system/DATA/Documents/06-games/security-game` for reference - material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/ - hollywood/`, 9 files) is already fully duplicated in our `Art/reference/hollywood/` - (with SIDECAR/BOARD copies) — nothing new there. -- What we lacked: its **AI concept renders** (`docs/screens/concepts/ - hollywood_boulevard/`, 12 PNGs + `.refgen.json`). Copied into - `Art/reference/el-capitan-concepts/` with a folder README. **Owner explicitly - said ignore licensing completely** — they are gitignored local development - reference, never committed or shipped; `.refgen.json` still records the model - and source photo per file for traceability. -- Use concepts for mood/lighting intent only; geometry truth stays with the photo - refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.). -- The security-game `realism/` A/B screenshots and `concepts/joke_shop/` are its - own engine/venue renders — not relevant to our 6840 modeling. +- Scanned `/run/media/system/DATA/Documents/06-games/security-game` for reference material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/ hollywood/`, 9 files) is already fully duplicated in our `Art/reference/hollywood/` (with SIDECAR/BOARD copies) — nothing new there. +- What we lacked: its **AI concept renders** (`docs/screens/concepts/ hollywood_boulevard/`, 12 PNGs + `.refgen.json`). Copied into `Art/reference/el-capitan-concepts/` with a folder README. **Owner explicitly said ignore licensing completely** — they are gitignored local development reference, never committed or shipped; `.refgen.json` still records the model and source photo per file for traceability. +- Use concepts for mood/lighting intent only; geometry truth stays with the photo refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.). +- The security-game `realism/` A/B screenshots and `concepts/joke_shop/` are its own engine/venue renders — not relevant to our 6840 modeling. ## 2026-11-08 — ODbL review closed; remaining active TODO items finished -- Wrote `Docs/odbl-review.md` (decision 2026-11-08): the shipped block geometry - (streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database - (ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("© - OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"), - ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox, - SHA-256). Share-alike is limited to the data layer; project code/art remain CC0 - (Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated - to ODbL. -- Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision - verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1 - patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height); - (2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays - deferred (tracked separately). +- Wrote `Docs/odbl-review.md` (decision 2026-11-08): the shipped block geometry (streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database (ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("© OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"), ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox, SHA-256). Share-alike is limited to the data layer; project code/art remain CC0 (Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated to ODbL. +- Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1 patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height); (2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays deferred (tracked separately). - Branch: odbl-review-0.1.0 (docs + TODO closure only). ## 2026-11-08 — placement release check; north-buildings camera was 9.5 km off -- Release check 2 (placement vs references): rendered demo, probed DemoMap world - positions, compared to Geo references. Matches: road 24.3 m (ref crossings - 20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero - frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings - + TCL/Tussaud's in verified OSM positions. -- **Bug found:** the `demo-north-buildings` camera sat at X -22800 (the buildings' - own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry: - TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799, - Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at - (X -22750, Y +1600) looking north; sky fraction 85% -> 41%. -- Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the - world footprint (FBX carries the geo offset). Probe by loading the level - explicitly (load_level) — a fresh commandlet starts on Untitled otherwise. +- Release check 2 (placement vs references): rendered demo, probed DemoMap world positions, compared to Geo references. Matches: road 24.3 m (ref crossings 20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings + - TCL/Tussaud's in verified OSM positions. +- **Bug found:** the `demo-north-buildings` camera sat at X -22800 (the buildings' own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry: TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799, Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at (X -22750, Y +1600) looking north; sky fraction 85% -> 41%. +- Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the world footprint (FBX carries the geo offset). Probe by loading the level explicitly (load_level) — a fresh commandlet starts on Untitled otherwise. ## 2026-11-08 — collision/seam audit done; QA self-test extended -- `-qaselftest` transect widened from 84 samples (3 lane centres) to **1456 - samples / 16 rows** including kerb/road/sidewalk seam Ys (probed: N side - -1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side - +718..+1447). 0 holes. -- **New wall probe:** ground-referenced horizontal 30 m trace at +160 cm. First - attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366) - -> every row reported a dist-0 blocker. Always ground the probe first. -- **Guard:** GigLineShotActor must skip rounds under `-qaselftest`, else - bQuitWhenDone closes the game before the transect phase (observed: game exited - at t=2s with "ShotActor: done (4 shots)" + RequestExit). -- Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect - 0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md. +- `-qaselftest` transect widened from 84 samples (3 lane centres) to **1456 samples / 16 rows** including kerb/road/sidewalk seam Ys (probed: N side -1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side +718..+1447). 0 holes. +- **New wall probe:** ground-referenced horizontal 30 m trace at +160 cm. First attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366) -> every row reported a dist-0 blocker. Always ground the probe first. +- **Guard:** GigLineShotActor must skip rounds under `-qaselftest`, else bQuitWhenDone closes the game before the transect phase (observed: game exited at t=2s with "ShotActor: done (4 shots)" + RequestExit). +- Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect 0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md. ## 2026-11-08 — setup logs gated behind a cvar (diagnostics item) -- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine - IN:" pushed/bound/errors) are now gated behind `gigline.SetupLogs` - (TAutoConsoleVariable, default **0** via `DefaultEngine.ini [ConsoleVariables]`). - Re-enable at runtime: `gigline.SetupLogs 1`. -- The `-qaselftest` QA logs stay ungated (deliberate release-check feature per - Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite - still all PASS (walk/reset/pause/transect 0 holes/wall 0-35). -- Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the - DefaultKeyMappings fix (PR #8). +- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine IN:" pushed/bound/errors) are now gated behind `gigline.SetupLogs` (TAutoConsoleVariable, default **0** via `DefaultEngine.ini [ConsoleVariables]`). Re-enable at runtime: `gigline.SetupLogs 1`. +- The `-qaselftest` QA logs stay ungated (deliberate release-check feature per Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite still all PASS (walk/reset/pause/transect 0 holes/wall 0-35). +- Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the DefaultKeyMappings fix (PR #8). ## 2026-11-08 — v0.1.0 released -- All six `0.1.0` release checks pass and are recorded in `Docs/release-checks.md`: - traversal (seam transect 0 holes), placement vs references, collision+packaging - (wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG), - fresh-checkout build, packaged Linux build with controls shown in-game. -- `CHANGELOG.md` gets the `0.1.0` entry; the completed `0.1.0` section was removed - from `TODO.md` (goal stays in `ROADMAP.md`); tag `v0.1.0` cut on `main`. +- All six `0.1.0` release checks pass and are recorded in `Docs/release-checks.md`: traversal (seam transect 0 holes), placement vs references, collision+packaging (wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG), fresh-checkout build, packaged Linux build with controls shown in-game. +- `CHANGELOG.md` gets the `0.1.0` entry; the completed `0.1.0` section was removed from `TODO.md` (goal stays in `ROADMAP.md`); tag `v0.1.0` cut on `main`. - Owner approved the regenerated screenshot set (PR #15) on 2026-11-08. -- THIRD_PARTY_NOTICES switched to checkbox statuses (`[x]`/`[P]`/`[ ]`); all tools - incl. Blosm, QGIS and Git LFS are now verified as tool-only. -- Next work: the `0.1.1` collision-detail patch (sidewalk outer edge 15.8 cm step, - crossing ramp gradient 10 %, kerb height assumption, traversal regression). +- THIRD_PARTY_NOTICES switched to checkbox statuses (`[x]`/`[P]`/`[ ]`); all tools incl. Blosm, QGIS and Git LFS are now verified as tool-only. +- Next work: the `0.1.1` collision-detail patch (sidewalk outer edge 15.8 cm step, crossing ramp gradient 10 %, kerb height assumption, traversal regression). ## 2026-11-08 — v0.1.1 released (collision patch) -- Sidewalk upstand now tapers full-at-kerb → zero at the building edge (removes the - 15.8 cm outer ledge). Crossing ramp run derives from `--max-ramp-grade` (8 % ⇒ - 1.63 m) instead of a fixed 1.5 m/8.7 %. Street + site rebuilt, site re-imported, - DemoMap rebuilt, packaged build verified. -- Pre-check tool fixes: coverage no longer shrinks with `--sample` (it used - `range(int(block_len))`), and it reports grade + location of the worst step. -- Residual, documented not hidden: 13.4 cm surface step at s≈198 (south) / 8.0 cm - at s≈251 (north) — a strip discontinuity near a centreline vertex, below the - 45 cm MaxStepHeight, so not blocking. -- Kerb height finalised at 0.15 m modelled / 0.13 m built (6 in LA standard; DEM - and photos can't resolve finer). +- Sidewalk upstand now tapers full-at-kerb → zero at the building edge (removes the 15.8 cm outer ledge). Crossing ramp run derives from `--max-ramp-grade` (8 % ⇒ 1.63 m) instead of a fixed 1.5 m/8.7 %. Street + site rebuilt, site re-imported, DemoMap rebuilt, packaged build verified. +- Pre-check tool fixes: coverage no longer shrinks with `--sample` (it used `range(int(block_len))`), and it reports grade + location of the worst step. +- Residual, documented not hidden: 13.4 cm surface step at s≈198 (south) / 8.0 cm at s≈251 (north) — a strip discontinuity near a centreline vertex, below the 45 cm MaxStepHeight, so not blocking. +- Kerb height finalised at 0.15 m modelled / 0.13 m built (6 in LA standard; DEM and photos can't resolve finer). ## 2026-11-09 — Mapbox building data as a second height source -- `Geo/tools/mapbox_buildings.py` (new, stdlib `urllib`) Tilequeries - `mapbox.mapbox-streets-v8` layer `building` at every building centroid in the - extract and merges the answer with our OSM height + source. Token comes from - `MAPBOX_ACCESS_TOKEN` in the ignored `.env`; responses cache to - `Geo/raw/mapbox-cache/`. -- **Mapbox terms restrict persisting/redistributing their data**, so `.gitignore` - now covers `/Geo/raw/mapbox-cache/` and `/Geo/verification/*mapbox*`. The tool - also refuses to cache a response that contains the token. +- `Geo/tools/mapbox_buildings.py` (new, stdlib `urllib`) Tilequeries `mapbox.mapbox-streets-v8` layer `building` at every building centroid in the extract and merges the answer with our OSM height + source. Token comes from `MAPBOX_ACCESS_TOKEN` in the ignored `.env`; responses cache to `Geo/raw/mapbox-cache/`. +- **Mapbox terms restrict persisting/redistributing their data**, so `.gitignore` now covers `/Geo/raw/mapbox-cache/` and `/Geo/verification/*mapbox*`. The tool also refuses to cache a response that contains the token. - **First run (72 buildings): 58 match, 5 close, 3 disagree, 6 non-building.** - - It independently classifies our roof/canopy/shelter volumes as `roof`/ - `shelter` — corroborating the `0.1.2` finding that they are not buildings. - - Remaining disagreements are untyped `building=yes`/`apartments` rear - structures where our fallback guess (2 × 3.2 m = 6.4 m) meets Mapbox's 3.0 m. - - Every "close" case is a `building:levels × 3.2 m` building where Mapbox is - **0.6 m lower per 6 levels** → Mapbox implies **3.1 m per level**, not 3.2. - Worth revisiting `LEVEL_HEIGHT` if a measured storey height ever lands. + - It independently classifies our roof/canopy/shelter volumes as `roof`/ `shelter` — corroborating the `0.1.2` finding that they are not buildings. + - Remaining disagreements are untyped `building=yes`/`apartments` rear structures where our fallback guess (2 × 3.2 m = 6.4 m) meets Mapbox's 3.0 m. + - Every "close" case is a `building:levels × 3.2 m` building where Mapbox is **0.6 m lower per 6 levels** → Mapbox implies **3.1 m per level**, not 3.2. Worth revisiting `LEVEL_HEIGHT` if a measured storey height ever lands. - Its query API surface: `height`, `min_height`, `type`, `extrude` per polygon. ## 2026-11-09 — Mapbox calibration adopted into the blockout - Owner decision: adopt Mapbox. Applied: - - `LEVEL_HEIGHT` 3.2 → **3.1 m** in `Geo/tools/block_height_report.py`, and the - Blosm `--level-height` default in `Art/blender/blockout/blosm_blockout.py`. - - `HEIGHT_OVERRIDES` in block_height_report: ways 1490910971, 1492058875, - 1492747608 → 3.0 m (Mapbox single-storey; the 2-level guess gave 6.4 m). - - `classify_height(tags, level_height, way_id)` now takes the way id so - overrides apply; `mapbox_buildings.py` passes it, so the cross-check agrees - with the height table. + - `LEVEL_HEIGHT` 3.2 → **3.1 m** in `Geo/tools/block_height_report.py`, and the Blosm `--level-height` default in `Art/blender/blockout/blosm_blockout.py`. + - `HEIGHT_OVERRIDES` in block_height_report: ways 1490910971, 1492058875, 1492747608 → 3.0 m (Mapbox single-storey; the 2-level guess gave 6.4 m). + - `classify_height(tags, level_height, way_id)` now takes the way id so overrides apply; `mapbox_buildings.py` passes it, so the cross-check agrees with the height table. - Re-running the cross-check: **66 match, 6 non-building, 0 close/disagree**. -- Geometry: blockout regenerated at 3.1 (object names preserved — the frontage - exporter maps `element.020`/`element`/`element.019`/named buildings, all still - present, 6/6 exported), frontage re-imported, DemoMap rebuilt, packaged build - `-qaselftest` all PASS. -- **Blosm adds roof mass** on top of `levels × levelHeight`: 6922 39.1 m vs table - 37.2, El Capitan Theatre 20.5 m vs 18.6 — both ~+1.9 m. That is a roof, not a - height error; the table is the tagged height, the mesh is storey + roof. -- Gotcha: regenerating the blockout is safe for names, but any future change to - the import must re-verify the 6/6 frontage export and the `element.*` mapping. +- Geometry: blockout regenerated at 3.1 (object names preserved — the frontage exporter maps `element.020`/`element`/`element.019`/named buildings, all still present, 6/6 exported), frontage re-imported, DemoMap rebuilt, packaged build `-qaselftest` all PASS. +- **Blosm adds roof mass** on top of `levels × levelHeight`: 6922 39.1 m vs table 37.2, El Capitan Theatre 20.5 m vs 18.6 — both ~+1.9 m. That is a roof, not a height error; the table is the tagged height, the mesh is storey + roof. +- Gotcha: regenerating the blockout is safe for names, but any future change to the import must re-verify the 6/6 frontage export and the `element.*` mapping. ## 2026-11-09 — hero colonnade proportion fixed (47.6 % → 53.1 %) -- The hero's colonnade (columns 2.00–7.95 + capitals 7.95–8.90) was 6.90 m of the - 14.50 m elevation = **47.6 %**, below the photographed 50–60 %, because the - entablature (3.40 m) and parapet (2.20 m) used 5.60 m of the height. -- Thinned ENTABLATURE_H 3.40 → 3.00 and PARAPET_H 2.20 → 1.80 in - `Tools/hero_facade_build.py`; the colonnade grows to **7.70 m = 53.1 %** while the - envelope, motto band and medallions keep their relationships (all positioned - relative to those two constants). -- **Gotcha (cost a bad build):** `hero_facade_build.py` takes `--out` for the - **FBX** and `--save-blend` for the `.blend`. Passing `--out .blend` writes - FBX bytes over the blend and Blender then reports "File format is not supported". - Correct invocation: `--out /tmp/gigline/hero-facade.fbx --save-blend Art/...blend - --report Art/...json`. -- Rebuilt hero → re-imported (`SM_HeroFacade`, bounds 2604 × 1257 × 1450 cm) → - DemoMap rebuilt → screenshots re-rendered. Note `ue_hero_facade_import.py` saves - the map, so `chmod u+w` the `.umap` first or the import silently does not persist. +- The hero's colonnade (columns 2.00–7.95 + capitals 7.95–8.90) was 6.90 m of the 14.50 m elevation = **47.6 %**, below the photographed 50–60 %, because the entablature (3.40 m) and parapet (2.20 m) used 5.60 m of the height. +- Thinned ENTABLATURE_H 3.40 → 3.00 and PARAPET_H 2.20 → 1.80 in `Tools/hero_facade_build.py`; the colonnade grows to **7.70 m = 53.1 %** while the envelope, motto band and medallions keep their relationships (all positioned relative to those two constants). +- **Gotcha (cost a bad build):** `hero_facade_build.py` takes `--out` for the **FBX** and `--save-blend` for the `.blend`. Passing `--out .blend` writes FBX bytes over the blend and Blender then reports "File format is not supported". Correct invocation: `--out /tmp/gigline/hero-facade.fbx --save-blend Art/...blend --report Art/...json`. +- Rebuilt hero → re-imported (`SM_HeroFacade`, bounds 2604 × 1257 × 1450 cm) → DemoMap rebuilt → screenshots re-rendered. Note `ue_hero_facade_import.py` saves the map, so `chmod u+w` the `.umap` first or the import silently does not persist. ## 2026-11-09 — AI detail plates from reference photos; floating windows fixed -- `Tools/facade_detail_concepts.py` generates architectural detail plates from one - local reference photograph via OpenRouter image models (default - `google/gemini-2.5-flash-image`; the sibling project uses the same route in - `tools/generate_textures.py` — chat/completions with a text + image_url content - part, image returned as `message.images[].image_url.url`). Eight studies, - `--list` free, `--all` generates, `-vN` never overwrites, `.refgen.json` sidecar - per image, all output under gitignored `Art/reference/facade-details/`. -- **Prompt gotcha:** the model refuses when the prompt describes features the - reference photograph does not show ("the image you sent appears to be a - decorative architectural detail, not an entrance with steps"). Prompts must - describe the *reference subject* and only restage light/people/surface. +- `Tools/facade_detail_concepts.py` generates architectural detail plates from one local reference photograph via OpenRouter image models (default `google/gemini-2.5-flash-image`; the sibling project uses the same route in `tools/generate_textures.py` — chat/completions with a text + image_url content part, image returned as `message.images[].image_url.url`). Eight studies, `--list` free, `--all` generates, `-vN` never overwrites, `.refgen.json` sidecar per image, all output under gitignored `Art/reference/facade-details/`. +- **Prompt gotcha:** the model refuses when the prompt describes features the reference photograph does not show ("the image you sent appears to be a decorative architectural detail, not an entrance with steps"). Prompts must describe the _reference subject_ and only restage light/people/surface. - **Output is real:** 1184×864 to 1344×768 PNGs, ~2 MB each. -- **Measured defect found and fixed:** hero window panes sat at Y −1.98 while the - mass front is Y −0.50 and the columns reach −1.02 — i.e. glazing floating 1.44 m - in front of the wall and ahead of the colonnade. Now `WINDOW_Y = −0.56` - (6 cm proud of the wall, behind the columns). Visible as dark openings in - `review-hero-facade-front`. -- Reminder: `.blend` is LFS read-only on checkout — `chmod u+w` or Blender fails - with "Cannot save blend file … not writable" (this bit again here). +- **Measured defect found and fixed:** hero window panes sat at Y −1.98 while the mass front is Y −0.50 and the columns reach −1.02 — i.e. glazing floating 1.44 m in front of the wall and ahead of the colonnade. Now `WINDOW_Y = −0.56` (6 cm proud of the wall, behind the columns). Visible as dark openings in `review-hero-facade-front`. +- Reminder: `.blend` is LFS read-only on checkout — `chmod u+w` or Blender fails with "Cannot save blend file … not writable" (this bit again here). ## 2026-11-09 — UE export hygiene: smoothing groups + facade collision -- **All our FBX exporters used `mesh_smooth_type="OFF"`**, which is why every Unreal - import logged "No smoothing group information was found for this mesh". The hero - now exports `"FACE"` and its round parts are shaded with - `shade_smooth_by_angle(35°)` before export: cylinders read as round instead of - faceted, and the import warning is gone (6 → 0). -- **The hero façade had no simple collision** (complex-only), so line traces and - capsule sweeps passed straight through it — the player could walk through the - wall of the demo. Caught by a new `-qaselftest` **facade occlusion probe** (40 m - sideways traces from the road centre at walking height). -- **Auto-generated convex collision on a 95-part 26 m façade is unusable**: it - produced coarse hulls that bulged into the street and the wall probe went - 0/35 → 2/35. Rejected in favour of `UseComplexAsSimple` via - `UGigLineAssetTools.set_complex_collision_as_simple` (same reason the terrain - uses it), saved on the asset. -- Probe expectations must mirror the staged buildings: the facade probe now carries - explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so - a gap between buildings is not reported as a defect. +- **All our FBX exporters used `mesh_smooth_type="OFF"`**, which is why every Unreal import logged "No smoothing group information was found for this mesh". The hero now exports `"FACE"` and its round parts are shaded with `shade_smooth_by_angle(35°)` before export: cylinders read as round instead of faceted, and the import warning is gone (6 → 0). +- **The hero façade had no simple collision** (complex-only), so line traces and capsule sweeps passed straight through it — the player could walk through the wall of the demo. Caught by a new `-qaselftest` **facade occlusion probe** (40 m sideways traces from the road centre at walking height). +- **Auto-generated convex collision on a 95-part 26 m façade is unusable**: it produced coarse hulls that bulged into the street and the wall probe went 0/35 → 2/35. Rejected in favour of `UseComplexAsSimple` via `UGigLineAssetTools.set_complex_collision_as_simple` (same reason the terrain uses it), saved on the asset. +- Probe expectations must mirror the staged buildings: the facade probe now carries explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so a gap between buildings is not reported as a defect. ## 2026-11-09 — one FBX export preset instead of six copies -- The `bpy.ops.export_scene.fbx(...)` argument block was duplicated in six scripts - (hero, block frontage, north buildings, site, scale test, MPFB character), and - all six carried `mesh_smooth_type="OFF"` — hence the "No smoothing group - information was found" warning on every import. -- Now `Tools/fbx_export_preset.py` holds `ue_metric_v1` and `ue_character_v1`: - `preset_kwargs()` (bpy-free, unit-tested) plus `export_static()` / - `export_character()` wrappers that import `bpy` lazily. Every caller imports it - and passes `selection=True/False`; the seven duplicated blocks are gone. -- Callers in `Tools/` resolve the module with - `sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))`; the Blender-side - `Art/blender/site/export_site_fbx.py` computes the repo root - (`..`, `..`, `..`) and joins `Tools`. Getting that wrong silently inserts - `Tools/Tools` — check the import path when adding a caller. -- Verified end to end: scale test, hero, frontage (6/6), site (7/7) and north - buildings all re-exported through the preset, re-imported with **0 smoothing - warnings**, DemoMap rebuilt and `-qaselftest` all PASS (walk, transect 0 holes, - wall probe 0/35, facade probe 0/9). -- `mpfb_character_export.py` was not re-run: it needs the MPFB add-on on - `BLENDER_USER_SCRIPTS` ("Add-on not loaded: bl_ext.blender_org.mpfb"), which is - pre-existing and unrelated; the character is not in `0.1.x` gameplay. +- The `bpy.ops.export_scene.fbx(...)` argument block was duplicated in six scripts (hero, block frontage, north buildings, site, scale test, MPFB character), and all six carried `mesh_smooth_type="OFF"` — hence the "No smoothing group information was found" warning on every import. +- Now `Tools/fbx_export_preset.py` holds `ue_metric_v1` and `ue_character_v1`: `preset_kwargs()` (bpy-free, unit-tested) plus `export_static()` / `export_character()` wrappers that import `bpy` lazily. Every caller imports it and passes `selection=True/False`; the seven duplicated blocks are gone. +- Callers in `Tools/` resolve the module with `sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))`; the Blender-side `Art/blender/site/export_site_fbx.py` computes the repo root (`..`, `..`, `..`) and joins `Tools`. Getting that wrong silently inserts `Tools/Tools` — check the import path when adding a caller. +- Verified end to end: scale test, hero, frontage (6/6), site (7/7) and north buildings all re-exported through the preset, re-imported with **0 smoothing warnings**, DemoMap rebuilt and `-qaselftest` all PASS (walk, transect 0 holes, wall probe 0/35, facade probe 0/9). +- `mpfb_character_export.py` was not re-run: it needs the MPFB add-on on `BLENDER_USER_SCRIPTS` ("Add-on not loaded: bl_ext.blender_org.mpfb"), which is pre-existing and unrelated; the character is not in `0.1.x` gameplay. ## 2026-11-09 — v0.1.2 released -- Cornice finished: projecting corona over a **52-dentil course** (0.18 × 0.30 × - 0.22 m at 0.45 m centres, z 12.12–12.34, 0.25 m clear of the corona). Hero is now - 3 044 tris across 148 parts, still 14.50 m. Verified: packaged `-qaselftest` all - PASS (transect 0 holes, wall probe 0/35, facade probe 0/9). -- `CHANGELOG.md` gets the `0.1.2` entry (added/changed/fixed/verified/limitations), - the `0.1.2` section is removed from `TODO.md`, `ROADMAP.md` status updated, tag - `v0.1.2` cut after the merge. -- Assumptions recorded rather than hidden: dentil spacing, base and abacus - dimensions are not resolvable in our reference photography. +- Cornice finished: projecting corona over a **52-dentil course** (0.18 × 0.30 × 0.22 m at 0.45 m centres, z 12.12–12.34, 0.25 m clear of the corona). Hero is now 3 044 tris across 148 parts, still 14.50 m. Verified: packaged `-qaselftest` all PASS (transect 0 holes, wall probe 0/35, facade probe 0/9). +- `CHANGELOG.md` gets the `0.1.2` entry (added/changed/fixed/verified/limitations), the `0.1.2` section is removed from `TODO.md`, `ROADMAP.md` status updated, tag `v0.1.2` cut after the merge. +- Assumptions recorded rather than hidden: dentil spacing, base and abacus dimensions are not resolvable in our reference photography. ## 2026-11-09 — elevation cameras levelled (straight verticals) -- archviz MUST: an elevation must not lean. `hero-facade-front` looked up ~4° from - 4.2 m at a 7.6 m target; `demo-spawn-view` and `demo-street-level` were pitched - too. All three now put the camera at the target's height, so - `FindLookAtRotation` yields pitch 0 and roll 0 and verticals are parallel by - construction. -- Do not try to *measure* verticality from the rendered PNG with a naive edge - scan: banners, windows and the parapet produce different edge sets at different - heights and the pairwise match is meaningless (it reported a 418 px "drift"). - Verify it geometrically in the camera list instead. +- archviz MUST: an elevation must not lean. `hero-facade-front` looked up ~4° from 4.2 m at a 7.6 m target; `demo-spawn-view` and `demo-street-level` were pitched too. All three now put the camera at the target's height, so `FindLookAtRotation` yields pitch 0 and roll 0 and verticals are parallel by construction. +- Do not try to _measure_ verticality from the rendered PNG with a naive edge scan: banners, windows and the parapet produce different edge sets at different heights and the pairwise match is meaningless (it reported a 418 px "drift"). Verify it geometrically in the camera list instead. ## 2026-11-09 — 0.1.3 packaging/setup fixes -- **`Tools/ue_common.py`**: `make_writable()` clears the read-only bit an LFS - `.umap` checks out with (otherwise `save_map()` returns False with no error and - the staged actors vanish), and `require_saved()` makes that failure visible. - `ue_hero_facade_import.py` and `ue_import_north_buildings.py` never did this — - which is why importing the hero appeared to succeed while saving nothing. - Verified by forcing `chmod u-w` on DemoMap first: the run logged - `map writable: chmod u+w … (was 0o444)`, then `save_map: True`, exit 0. -- Gotcha while adding it: `ue_hero_facade_import.py` had **no** `import os`/`sys` - (only `unreal`), so the sys.path line raised `NameError: name 'sys' is not - defined` inside the commandlet — an exit code of 255 with no obvious cause. -- Docs: `build.md` gained the current pipeline table (7 site FBXs, frontage, north, - hero, map), the `Builds/GigLineQA` archive, the packaged-log path, `-qaselftest` - usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and - documents the two optional API keys; architecture 07 + combined doc corrected the - build-output path. +- **`Tools/ue_common.py`**: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False with no error and the staged actors vanish), and `require_saved()` makes that failure visible. `ue_hero_facade_import.py` and `ue_import_north_buildings.py` never did this — which is why importing the hero appeared to succeed while saving nothing. Verified by forcing `chmod u-w` on DemoMap first: the run logged `map writable: chmod u+w … (was 0o444)`, then `save_map: True`, exit 0. +- Gotcha while adding it: `ue_hero_facade_import.py` had **no** `import os`/`sys` (only `unreal`), so the sys.path line raised `NameError: name 'sys' is not defined` inside the commandlet — an exit code of 255 with no obvious cause. +- Docs: `build.md` gained the current pipeline table (7 site FBXs, frontage, north, hero, map), the `Builds/GigLineQA` archive, the packaged-log path, `-qaselftest` usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and documents the two optional API keys; architecture 07 + combined doc corrected the build-output path. ## 2026-11-09 — 0.1.4 materials + architecture-camera pass -- **Material graphs**: `ensure_material()` now supports masonry coursing - (`courses_m`) and roughness variation. **UE's `WorldPosition` is in - CENTIMETRES**, so a course height in metres must be ×100 — the first attempt - divided by 0.55 and produced coursing 100× too fine to see. -- **`connect_material_expressions` returns False instead of raising**, so an - unknown pin name leaves an input unconnected and the material silently compiles - with a default (a mask of 0 is invisible). All links now go through a `link()` - helper that logs failures. There is also **no `MaterialExpressionLess`**; use - `Frac → Divide → OneMinus → Clamp` for a soft threshold band, or - `MaterialExpressionStep`/`SmoothStep` if a hard one is wanted. -- **Material assignment was slot-0-only**, which on the hero is the *banner* — the - stone, shadowed stone and glazing kept their flat Blender materials. Assignment - is now per slot, keyed on the imported slot material's name - (`glass`/`banner`/`stoneshadow`/`stone`/`roof`/`wall`). Hero: 4/4 slots. -- New materials: `M_FacadeStoneShadow` (same coursing, a stop darker), - `M_FacadeGlass` (roughness 0.14, specular 0.62 ≈ IOR 1.5 expressed without a - refraction pass), `M_FacadeBanner` (the one saturated surface). -- **Camera FOV**: levelling the elevation cameras left the building small in frame - (39 % sky). Added optional per-shot `ShotFieldOfView` to `AGigLineShotActor` and - a 4th element on the shot tuples; a long lens (42–55°) fills the frame from - across the street while the camera stays level, so verticals stay parallel. - `review-hero-facade-front` sky 39 → 20 %, `demo-spawn-view` 23 → 16 %. +- **Material graphs**: `ensure_material()` now supports masonry coursing (`courses_m`) and roughness variation. **UE's `WorldPosition` is in CENTIMETRES**, so a course height in metres must be ×100 — the first attempt divided by 0.55 and produced coursing 100× too fine to see. +- **`connect_material_expressions` returns False instead of raising**, so an unknown pin name leaves an input unconnected and the material silently compiles with a default (a mask of 0 is invisible). All links now go through a `link()` helper that logs failures. There is also **no `MaterialExpressionLess`**; use `Frac → Divide → OneMinus → Clamp` for a soft threshold band, or `MaterialExpressionStep`/`SmoothStep` if a hard one is wanted. +- **Material assignment was slot-0-only**, which on the hero is the _banner_ — the stone, shadowed stone and glazing kept their flat Blender materials. Assignment is now per slot, keyed on the imported slot material's name (`glass`/`banner`/`stoneshadow`/`stone`/`roof`/`wall`). Hero: 4/4 slots. +- New materials: `M_FacadeStoneShadow` (same coursing, a stop darker), `M_FacadeGlass` (roughness 0.14, specular 0.62 ≈ IOR 1.5 expressed without a refraction pass), `M_FacadeBanner` (the one saturated surface). +- **Camera FOV**: levelling the elevation cameras left the building small in frame (39 % sky). Added optional per-shot `ShotFieldOfView` to `AGigLineShotActor` and a 4th element on the shot tuples; a long lens (42–55°) fills the frame from across the street while the camera stays level, so verticals stay parallel. `review-hero-facade-front` sky 39 → 20 %, `demo-spawn-view` 23 → 16 %. ## 2026-11-09 — facade texture maps (0.1.4) -- `Tools/generate_facade_textures.py` writes deterministic 512² base-colour and - roughness maps (ashlar stone, stucco, asphalt, concrete) with a pure-zlib PNG - encoder and hash-based value noise — no numpy/Pillow, ~14 s for all eight, and - byte-identical on a re-run (checked by md5sum). -- `ue_build_demo_map.py` imports them to `/Game/Hollywood/Textures` and samples - them **world-aligned**: WorldPosition (cm) → ×(1/tile_cm) → ComponentMask(R,G) - → TextureSample UVs. `tile_cm` per material: stone 440, stucco 260, asphalt 900, - concrete 250. -- Gotchas: `MaterialExpressionTextureSample`'s UV input pin is **"UVs"**, and the - output pin for colour is **"RGB"** / roughness **"R"**. `ComponentMask` pins are - R/G/B/A booleans. A textured material skips the graph coursing on purpose — - overlaying both would double the course pattern. -- The script needs `ROOT` in `ue_build_demo_map.py` and `sys` imported; forgetting - either fails with `NameError` and an exit code of 0 from the commandlet (always - read the result file under /tmp/gigline). +- `Tools/generate_facade_textures.py` writes deterministic 512² base-colour and roughness maps (ashlar stone, stucco, asphalt, concrete) with a pure-zlib PNG encoder and hash-based value noise — no numpy/Pillow, ~14 s for all eight, and byte-identical on a re-run (checked by md5sum). +- `ue_build_demo_map.py` imports them to `/Game/Hollywood/Textures` and samples them **world-aligned**: WorldPosition (cm) → ×(1/tile_cm) → ComponentMask(R,G) → TextureSample UVs. `tile_cm` per material: stone 440, stucco 260, asphalt 900, concrete 250. +- Gotchas: `MaterialExpressionTextureSample`'s UV input pin is **"UVs"**, and the output pin for colour is **"RGB"** / roughness **"R"**. `ComponentMask` pins are R/G/B/A booleans. A textured material skips the graph coursing on purpose — overlaying both would double the course pattern. +- The script needs `ROOT` in `ue_build_demo_map.py` and `sys` imported; forgetting either fails with `NameError` and an exit code of 0 from the commandlet (always read the result file under /tmp/gigline). ## 2026-11-09 — façade module kit (0.1.4) -- `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay** and - **3.10 m storey**: pilaster, sill, lintel, window bay, shopfront bay, door bay, - cornice, parapet + coping = 16 meshes, exported per-object with - `fbx_export_preset` plus a JSON part report (dims + evidence per piece). -- `Tools/ue_import_facade_kit.py` → `/Game/Hollywood/Kit/*`, complex-as-simple - collision (a convex hull would fence off the shopfront glazing). -- `Tools/ue_apply_facade_kit.py` lays the kit out from each building's own bbox: - `bays = round(width / 496 cm)`, storeys from height minus the 310 cm ground - floor. **638 pieces** over six frontage buildings + both north buildings. -- **Bug worth remembering:** the first window piece was modelled entirely at +Y - (into the wall), so every window sat *inside* the solid blockout wall and was - invisible in every render while the placement maths looked right. The kit now - projects the surround 3 cm proud with the glazing 6 cm behind its face — a - solid-wall blockout cannot carry a real recess, so the piece has to project. -- Blender gotcha: clearing the scene *including materials* invalidates handles - already taken ("StructRNA of type Material has been removed"); clear objects and - orphan meshes only, and create the material after the clear. -- The kit meshes are spawned at the world origin with their position baked into - the mesh (like the frontage exports), so placement uses the bbox directly and - must not add the actor location. +- `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay** and **3.10 m storey**: pilaster, sill, lintel, window bay, shopfront bay, door bay, cornice, parapet + coping = 16 meshes, exported per-object with `fbx_export_preset` plus a JSON part report (dims + evidence per piece). +- `Tools/ue_import_facade_kit.py` → `/Game/Hollywood/Kit/*`, complex-as-simple collision (a convex hull would fence off the shopfront glazing). +- `Tools/ue_apply_facade_kit.py` lays the kit out from each building's own bbox: `bays = round(width / 496 cm)`, storeys from height minus the 310 cm ground floor. **638 pieces** over six frontage buildings + both north buildings. +- **Bug worth remembering:** the first window piece was modelled entirely at +Y (into the wall), so every window sat _inside_ the solid blockout wall and was invisible in every render while the placement maths looked right. The kit now projects the surround 3 cm proud with the glazing 6 cm behind its face — a solid-wall blockout cannot carry a real recess, so the piece has to project. +- Blender gotcha: clearing the scene _including materials_ invalidates handles already taken ("StructRNA of type Material has been removed"); clear objects and orphan meshes only, and create the material after the clear. +- The kit meshes are spawned at the world origin with their position baked into the mesh (like the frontage exports), so placement uses the bbox directly and must not add the actor location. ## 2026-11-09 — CRITICAL: blockout must be regenerated with --origin -- Regenerating the blockout in 0.1.2 (for the 3.1 m storey height) was run **without - `--origin`**, so Blosm centred on the extract instead of fitting the project frame. - The whole frontage shifted about **119 m in X and 2.4 m in Y**, and the hero ended - up outside the 6902/6838 gap — the "verified placement" was undone silently, and - every render still looked like a plausible block. -- Correct invocation (README documents it, and the affine fit prints - `frame correction residual: max 0.0 cm`): - `blosm_blockout.py --osm … --bounds 34.1006,-118.3422,34.1020,-118.3370 - --level-height 3.1 --origin 34.101545,-118.338718 --assets-dir … --out …` -- After re-export + re-import, the frontage spans are back to the verified values: - 6922 −24276..−17700, 6902 −17749..−14591, **hero −14644..−12028 between them**, - theatre −12082..−8210, and the north buildings at −22889..−18442 / −27071..−22799. -- **`ue_import_north_buildings.py` also lacked `import sys`**, so its sys.path line - raised NameError and the import silently did nothing (exit 255). Same trap as the - hero importer: these scripts imported only `unreal`. -- Lesson: after any blockout regeneration, probe the staged buildings' world X spans - and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before - trusting a render. A shift of this size is invisible in a screenshot. +- Regenerating the blockout in 0.1.2 (for the 3.1 m storey height) was run **without `--origin`**, so Blosm centred on the extract instead of fitting the project frame. The whole frontage shifted about **119 m in X and 2.4 m in Y**, and the hero ended up outside the 6902/6838 gap — the "verified placement" was undone silently, and every render still looked like a plausible block. +- Correct invocation (README documents it, and the affine fit prints `frame correction residual: max 0.0 cm`): `blosm_blockout.py --osm … --bounds 34.1006,-118.3422,34.1020,-118.3370 --level-height 3.1 --origin 34.101545,-118.338718 --assets-dir … --out …` +- After re-export + re-import, the frontage spans are back to the verified values: 6922 −24276..−17700, 6902 −17749..−14591, **hero −14644..−12028 between them**, theatre −12082..−8210, and the north buildings at −22889..−18442 / −27071..−22799. +- **`ue_import_north_buildings.py` also lacked `import sys`**, so its sys.path line raised NameError and the import silently did nothing (exit 255). Same trap as the hero importer: these scripts imported only `unreal`. +- Lesson: after any blockout regeneration, probe the staged buildings' world X spans and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before trusting a render. A shift of this size is invisible in a screenshot. ## 2026-11-09 — kit naming + topology validation (blender-modeler checklist) -- Kit assets now follow the Unreal convention: the Blender object keeps its readable - name (`Kit_Pilaster`), the exported asset and file are `SM_Kit_Pilaster`. The - kit loader strips the `SM_` prefix so the layout code still addresses pieces by - short name. -- `facade_kit_build.py::validate()` runs the blender-modeler cleanup checklist on - every piece before handoff — non-manifold edges, loose vertices, triangle count — - and records the result in the part report. Current kit: 16 pieces, **all - manifold, no loose geometry, 12 tris each**. -- `ue_import_facade_kit.py` now deletes kit assets that the current build no longer - produces, so a rename cannot leave two generations of the same piece in the - folder (the rename from `Kit_*` to `SM_Kit_*` did exactly that until it did). +- Kit assets now follow the Unreal convention: the Blender object keeps its readable name (`Kit_Pilaster`), the exported asset and file are `SM_Kit_Pilaster`. The kit loader strips the `SM_` prefix so the layout code still addresses pieces by short name. +- `facade_kit_build.py::validate()` runs the blender-modeler cleanup checklist on every piece before handoff — non-manifold edges, loose vertices, triangle count — and records the result in the part report. Current kit: 16 pieces, **all manifold, no loose geometry, 12 tris each**. +- `ue_import_facade_kit.py` now deletes kit assets that the current build no longer produces, so a rename cannot leave two generations of the same piece in the folder (the rename from `Kit_*` to `SM_Kit_*` did exactly that until it did). ## 2026-11-09 — visual comparison loop; screenshots are demo-only -- `Tools/visual_loop.py` closes the reference → render → feedback loop. - `--verify` gates captures against `Tools/visual_targets.json` (blank, tone-less, - wrong-size fail); `--compare` measures luma, RMS contrast, edge density, palette - distance and a 3x3 luma grid against the reference and prints a 0–1 score; - `--iteration N` writes `Art/reference/iterations/N/{metrics.json,feedback.md}`. - The feedback names the skill to consult, so the next modelling pass starts from a - measurement rather than an opinion. -- **Iterations are gitignored** (`/Art/reference/iterations/`); only confirmed shots - are promoted into `Screenshots/`. A test asserts the ignore rule and that every - target is a demo-map shot. -- `Screenshots/` now holds **demo-map captures only** — the review staging map is no - longer rendered for review. `render_screenshots.sh --verify` renders the demo map, - collects the shots and runs the verification gate in one step. -- **PIL gotchas:** `FIND_EDGES` leaves its own border ring at 128 (236/3600 px on a - 60x60 frame = 6.5% "detail" on a perfectly flat image), so crop **after** - filtering — cropping before does not help, the new border gets the same - treatment. Palette shares must divide by the full-image area, not the cropped one. -- First comparison (iteration 2): spawn view 0.70, street level 0.35, crossing 0.41, - north buildings 0.34. Edge density is the dominant gap everywhere (0.04–0.11 - against 0.20–0.32 in the references) — the render still lacks the relief the - photographs carry, which is exactly what the `0.1.4` façade work is for. +- `Tools/visual_loop.py` closes the reference → render → feedback loop. `--verify` gates captures against `Tools/visual_targets.json` (blank, tone-less, wrong-size fail); `--compare` measures luma, RMS contrast, edge density, palette distance and a 3x3 luma grid against the reference and prints a 0–1 score; `--iteration N` writes `Art/reference/iterations/N/{metrics.json,feedback.md}`. The feedback names the skill to consult, so the next modelling pass starts from a measurement rather than an opinion. +- **Iterations are gitignored** (`/Art/reference/iterations/`); only confirmed shots are promoted into `Screenshots/`. A test asserts the ignore rule and that every target is a demo-map shot. +- `Screenshots/` now holds **demo-map captures only** — the review staging map is no longer rendered for review. `render_screenshots.sh --verify` renders the demo map, collects the shots and runs the verification gate in one step. +- **PIL gotchas:** `FIND_EDGES` leaves its own border ring at 128 (236/3600 px on a 60x60 frame = 6.5% "detail" on a perfectly flat image), so crop **after** filtering — cropping before does not help, the new border gets the same treatment. Palette shares must divide by the full-image area, not the cropped one. +- First comparison (iteration 2): spawn view 0.70, street level 0.35, crossing 0.41, north buildings 0.34. Edge density is the dominant gap everywhere (0.04–0.11 against 0.20–0.32 in the references) — the render still lacks the relief the photographs carry, which is exactly what the `0.1.4` façade work is for. ## 2026-11-09 — theatre marquee + blade sign -- `Tools/theatre_trim_build.py` builds the El Capitan Theatre's signature features in - **world coordinates**: marquee canopy 12.0 × 2.60 m at a 4.60 m soffit, illuminated - fascia, recessed soffit, vertical blade sign 1.30 × 5.20 m with frame and bracket. - `Tools/ue_stage_theatre_trim.py` imports to `/Game/Hollywood/Theatre` and stages one - actor per piece (single spawn at the origin — no placement arithmetic). +- `Tools/theatre_trim_build.py` builds the El Capitan Theatre's signature features in **world coordinates**: marquee canopy 12.0 × 2.60 m at a 4.60 m soffit, illuminated fascia, recessed soffit, vertical blade sign 1.30 × 5.20 m with frame and bracket. `Tools/ue_stage_theatre_trim.py` imports to `/Game/Hollywood/Theatre` and stages one actor per piece (single spawn at the origin — no placement arithmetic). - **Two placement bugs, both invisible in a render:** - 1. **Axis sign.** Blender +Y is Unreal −Y, so a face at Unreal Y +1001 is Blender - Y −10.01. Using +10.01 put the whole marquee on the far side of the boulevard. - 2. **Absolute Z.** Trim heights were absolute while the pavement at the theatre is - 4.56 m above the project datum, so the canopy hung 5 cm above the pavement — the - QA wall probe caught it as a knee-height blocker. `GROUND_Z` now offsets every - height. -- Wall probe: a scan row at Y 1000 sat ~1 cm from the frontage building line and - grazed the theatre wall once the kit's proud shopfronts went in. Rows now sample the - street envelope only (−1400, −167, 800, −1382, 718). + 1. **Axis sign.** Blender +Y is Unreal −Y, so a face at Unreal Y +1001 is Blender Y −10.01. Using +10.01 put the whole marquee on the far side of the boulevard. + 2. **Absolute Z.** Trim heights were absolute while the pavement at the theatre is 4.56 m above the project datum, so the canopy hung 5 cm above the pavement — the QA wall probe caught it as a knee-height blocker. `GROUND_Z` now offsets every height. +- Wall probe: a scan row at Y 1000 sat ~1 cm from the frontage building line and grazed the theatre wall once the kit's proud shopfronts went in. Rows now sample the street envelope only (−1400, −167, 800, −1382, 718). + +## 2026-11-09 — 0.1.4 signed off; 0.1.5 created for the carried geometry + +- Owner reviewed the four demo captures locally and approved the set. Signoff recorded in `Docs/reviews/0.1.4-review.md` (7/7 items) and in the per-shot `Approved` column of `Screenshots/README.md`. +- The three geometry items that `0.1.4` did not finish — retail neighbours from their own references, hero fluting/motto band, blockout cleaning — move to a new **`0.1.5` Façade finishing** patch rather than growing this one, plus a much-needed **1440p performance re-measure** (625 kit actors have never been measured; `0.1.0` measured 414 fps). +- Decision recorded because it sets a precedent: finish and sign off a version when its _stated_ scope is done, and carry leftover scope into the next patch — do not let a patch accumulate unmeasured geometry. diff --git a/Screenshots/README.md b/Screenshots/README.md index 9669d96..f22ad24 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -16,14 +16,14 @@ Stages the demo shot cameras, runs the game on `/Game/Hollywood/DemoMap`, copies Each shot has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) — the content the capture must show — and a verification state produced by `--verify`: it fails if the frame is blank, the wrong size, or has no tonal range. -| # | File | View | Expected content | Verified | -| --- | --- | --- | --- | --- | -| 1 | `demo/review-demo-spawn-view.png` | north sidewalk | The whole south block: the neoclassical hero façade between its neighbours, both sidewalks and the road in the foreground | ☑ 2026-11-09 | -| 2 | `demo/review-demo-street-level.png` | eye height, north side | Street level: the hero colonnade with the adjacent frontage buildings either side, shopfronts at pavement level | ☑ 2026-11-09 | -| 3 | `demo/review-demo-crossing.png` | along the crosswalk | The designated mid-block crossing (s=130 m, UE X −14956): zebra bars on the asphalt, south frontage and colonnade behind | ☑ 2026-11-09 | -| 4 | `demo/review-demo-north-buildings.png` | across the Boulevard | The two north-side buildings (TCL Chinese Theatre, Madame Tussaud's) from the south side, full height in frame | ☑ 2026-11-09 | +| # | File | View | Expected content | Verified | Approved | +| --- | --- | --- | --- | --- | --- | +| 1 | `demo/review-demo-spawn-view.png` | north sidewalk | The whole south block: the neoclassical hero façade between its neighbours, both sidewalks and the road in the foreground | ☑ 2026-11-09 | ☑ 2026-11-09 | +| 2 | `demo/review-demo-street-level.png` | eye height, north side | Street level: the hero colonnade with the adjacent frontage buildings either side, shopfronts at pavement level | ☑ 2026-11-09 | ☑ 2026-11-09 | +| 3 | `demo/review-demo-crossing.png` | along the crosswalk | The designated mid-block crossing (s=130 m, UE X −14956): zebra bars on the asphalt, south frontage and colonnade behind | ☑ 2026-11-09 | ☑ 2026-11-09 | +| 4 | `demo/review-demo-north-buildings.png` | across the Boulevard | The two north-side buildings (TCL Chinese Theatre, Madame Tussaud's) from the south side, full height in frame | ☑ 2026-11-09 | ☑ 2026-11-09 | -Approval is per shot, in the table above. Add a row when a shot is added; remove the file in the same change when a shot is retired. +Approval is per shot, in the table above (owner review of the `0.1.4` set, recorded in [`Docs/reviews/0.1.4-review.md`](../Docs/reviews/0.1.4-review.md)). Add a row when a shot is added; remove the file in the same change when a shot is retired. ## The iteration loop (uncommitted) diff --git a/TODO.md b/TODO.md index 5af1dab..00f551b 100644 --- a/TODO.md +++ b/TODO.md @@ -9,20 +9,19 @@ Actionable task list for this project. This file is the single, central place fo - When all steps in a version section are done: write a `CHANGELOG.md` entry, then remove the version section from this file. - Release gate: a version is tagged only on the owner's explicit go-ahead once its release checks pass, not automatically when the checkboxes close. -## 0.1.4 — Proper building façades (patch) +## 0.1.5 — Façade finishing (patch) -Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the façade module kit — bay, pilaster, cornice, parapet, window, shopfront, door — as reusable node groups), **archviz** (real-world scale and façade composition), **materials** (procedural brick, concrete, stone, render, glass — the ported generators), **texture-workflow** (AO/curvature/normal bakes and decals for the motto band and medallions), **uv-workflow** (texel density and modular UV layouts for the kit), **scene-assembly** (assembling kit pieces onto the fifteen block buildings), **lookdev** (screenshot-comparison loop until shading reads correctly), **historical-worlds** or **realistic-style** (1920s Hollywood Blvd neoclassical direction), **asset-optimization** + **unreal-export** (pre-export validation, `ue_metric_v1` preset), **qa-review** (final gate before the `Tools/render_screenshots.sh` review). +Suggested blender skills: **references** (reference-image-match against the photographs), **historical-worlds** / **archviz** (1920s Hollywood Blvd neoclassical detail), **materials** and **lookdev** for the remaining surface work. -### Patch tasks - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `facades-0.1.4`) +The `0.1.4` kit, materials and theatre trim are signed off; these are the geometry items that were carried forward rather than grown inside that patch. -- [ ] **Finish the Blosm blockout cleaning/detailing (moved from the deferred `0.1.0` item).** Import state: `Art/blender/blockout/blockout-2026-11-06.blend` (86 meshes, EPSG:26911, 0.0 cm affine residual) with cleaning tooling in `Geo/tools/` and `Art/blender/blockout/blosm_blockout.py`. To do here: (a) untextured roofs/facades → **Replace placeholder materials** below; (b) Blosm `setupScript` façade styles → **Give OSM buildings a façade style** below; (c) the 3 of 15 buildings lacking height data are the `0.1.2` placeholder-heights item. -- [x] **Define the façade module.** **Done 2026-11-09.** `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay**: pilaster, sill, lintel, window bay (surround + inset glazing), shopfront bay (piers, glass, fascia), door bay (jambs, door, threshold), cornice and parapet + coping — 16 meshes, each exported through the shared `ue_metric_v1` preset with a JSON part report recording dims and evidence. `Tools/ue_import_facade_kit.py` imports to `/Game/Hollywood/Kit`. `Tools/ue_apply_facade_kit.py` assembles it from each building's own bounding box: **638 pieces placed across the six frontage buildings and both north buildings** (6922 = 13 bays × 11 storeys, theatre 8 × 5, TCL and Tussaud's 9 × 4, etc.). First pass put the window bays inside the solid wall (invisible) — the kit now projects the surround 3 cm proud with glazing 6 cm behind it. -- [A] **Replace placeholder materials.** **In progress 2026-11-09.** `ensure_material()` now builds masonry coursing (world-space course height + joint band + tint, corrected for UE's centimetre WorldPosition) and roughness variation; new `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5 without a refraction pass) and `M_FacadeBanner`; assignment is per **slot** keyed on the imported slot name (it was slot-0 only, which on the hero is the banner, so stone/glass/shadow were still flat). Hero: 4/4 slots, frontage and north buildings 2/2. Remaining: bake real texture maps (brick/stone/asphalt generators) rather than graph-only detail, and give the frontage shopfront line its own material. -- [x] **Give OSM buildings a façade style.** **Closed as superseded 2026-11-09.** The Blosm `setupScript` route would build a second, competing façade system next to the module kit defined in the previous task. The kit already gives every staged building a real elevation (shopfront line, window rhythm, pilasters, cornice, parapet) from a single module, so a Blosm style script would duplicate it with a different vocabulary and a different set of assumptions. Revisit only if the kit cannot express a building the photographs demand. -- [ ] **Hero façade detail pass.** Ionic capitals and fluting, the parapet motto band and medallions as geometry or decal, window rhythm behind the colonnade, and the three portal flights at their photographed proportions. -- [A] **El Capitan Theatre (6838) and the retail neighbours.** **Theatre trim done 2026-11-09:** `Tools/theatre_trim_build.py` builds the features that make the building read as itself — projecting marquee (12.0 × 2.60 m canopy at a 4.60 m soffit), illuminated fascia, recessed soffit, and a vertical blade sign (1.30 × 5.20 m) with frame and bracket; `Tools/ue_stage_theatre_trim.py` imports and stages it with site materials. Two bugs fixed en route: the trim was mirrored to the far side of the boulevard (Blender +Y is Unreal −Y) and the marquee sat 5 cm above the pavement (absolute Z where the pavement is 4.56 m above the datum). **Still open:** the retail neighbours modelled from the 18-frame reference set. -- [ ] Review every façade change with `Tools/render_screenshots.sh` before tagging. -- [ ] Tag `v0.1.4` and write `CHANGELOG.md` entry; then remove this section. +### Patch tasks + +- [ ] **Retail neighbours (6822 Budget, 6806–6820, 6800 Lee Drug) from their own references.** The 18-frame set is the El Capitan Theatre, so it cannot stand in for these façades. Fetch or shoot per-building references first, then model the shopfront line and upper storeys rather than relying on the kit alone. +- [ ] **Hero façade: fluting and the motto band as geometry.** The colonnade is plain cylinders and the motto/medallions are flat panels; both are legible in the reference photography. +- [ ] **Blosm blockout cleaning pass.** Roofs and facades remain untextured volumes and the `setupScript` styles stay unused; with the kit carrying the elevations, this is now cleanup rather than a second façade system. +- [ ] **Re-measure 1440p performance** with 625 kit actors and 8 generated textures in the map. `0.1.0` measured 414 fps average, so there is headroom, but it has not been re-run since. +- [ ] Tag `v0.1.5` and write `CHANGELOG.md` entry; then remove this section. ## 0.2.0 — MPFB player body, third-person camera, basic animation (weeks 7–9) -- 2.47.3 From 70d9cb48581bf8300728fe170f9b094ca102da21 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 20:29:07 +0100 Subject: [PATCH 13/13] docs(review): consolidate the two 0.1.4 review documents into one Docs/qa/0.1.4-facade-qa.md contained the QA-gate detail (budgets, defect table, limitations) and Docs/reviews/0.1.4-review.md the signoff and images. One document now: short intro, the ticked signoff list, the four embedded captures, and a compact appendix holding the verified behaviour and placement, the defect table with root causes, and the limitations. All four image links verified; no other file referenced the removed document. --- Docs/qa/0.1.4-facade-qa.md | 81 ------------------------------------ Docs/reviews/0.1.4-review.md | 43 +++++++++++++++++-- 2 files changed, 40 insertions(+), 84 deletions(-) delete mode 100644 Docs/qa/0.1.4-facade-qa.md diff --git a/Docs/qa/0.1.4-facade-qa.md b/Docs/qa/0.1.4-facade-qa.md deleted file mode 100644 index 7a7cf0c..0000000 --- a/Docs/qa/0.1.4-facade-qa.md +++ /dev/null @@ -1,81 +0,0 @@ -# QA review — `0.1.4` façade work - -Checklist gate for the façade module kit, the replacement materials and the corrected blockout frame. Written per the `qa-review` skill: budgets, the screenshots that carry the claim, defects by severity, and an explicit verdict. - -Date: 2026-11-09 · Branch: `facades-0.1.4` · Build under test: packaged `GigLineQA`, Development, UE 5.8.4, Vulkan SM5. - -## Result: SHIP WITH NOTES - -The geometry, placement and budgets are verified by measurement. The **visual** half of this review is **not** complete: nobody has looked at the rendered set since the kit went on, so the reference-match verdict is pending a human. The notes at the end are what that review has to confirm. - -## Budgets - -| Asset group | Count | Triangles | Texture | Notes | -| --- | --: | --: | --- | --- | -| Hero façade | 148 parts, 1 mesh | 3 044 | none (graph materials) | was 1 892 before the Ionic detail | -| Façade kit | 16 meshes | 12 tris each = 192 | none | boxes; complex-as-simple collision | -| Kit instances in `DemoMap` | 625 actors | ~7 500 | shared materials | 8 buildings | -| Frontage buildings | 6 meshes | blockout volumes | 8 × 512² maps | `M_FacadeStone`, `M_Stucco`, … | -| North buildings | 2 meshes | blockout volumes | shared | TCL, Tussaud's | -| Site meshes | 7 | 55 k tris (terrain-dominated) | shared | terrain/road/sidewalks/kerbs/crossing | - -Texture set: `T_StoneAshlar`, `T_Stucco`, `T_Asphalt`, `T_Concrete`, each `_BC` + `_R` at 512², deterministic and regenerable. - -## Evidence - -Behaviour, packaged build (`-qaselftest`): - -``` -walk moved 761 cm in 2 s -> PASS (Z 456 -> 456) -transect 1456 samples, 0 holes -> PASS -wall probe 0/35 blocking hits inside 21 m -> PASS -facade probe 0/9 directions without a blocking wall -> PASS -RESULT walk=1 reset=1 pause=1 unpause=1 -> PASS -``` - -Geometry and placement, probed from the live map: - -- Frontage spans match the verified block: 6922 −24276..−17700, 6902 −17749..−14591, **hero −14644..−12028 between them**, theatre −12082..−8210, 6822 −7854..−7252, 6806 −6608..−3307, 6800 −3320..−1437. North: TCL −22889..−18442, Tussaud's −27071..−22799. -- Kit placed from world bounds (`get_actor_bounds`), e.g. theatre pieces at −12082..−8694, inside the theatre's own span. -- Kit module **4.96 m**, storey **3.10 m**, door **2.45 m**, window **1.60 × 1.85 m**. -- Kit topology: 16 pieces, **all manifold**, no loose vertices, 12 tris each. -- Asset hygiene: `/Game/Hollywood/Kit` holds exactly 16 `SM_` meshes and no leftover auto-imported materials. - -Screenshots: the committed set in [`Screenshots/`](../Screenshots/README.md), all 14 re-rendered for this state — hero shots 8–10 and demo views 11–14 carry the change. - -## Defects - -### Blockers - -None outstanding. Two were found and fixed during this cycle: - -- **Wrong frame (fixed).** The `0.1.2` blockout was regenerated without `--origin`, shifting the whole frontage ≈119 m in X and 2.4 m in Y and dropping the hero out of the 6902/6838 gap. Regenerated with `--origin 34.101545,-118.338718` (affine residual 0.0 cm); spans re-verified against [`Geo/verification/2026-11-06-el-capitan-block.md`](../Geo/verification/2026-11-06-el-capitan-block.md). -- **Kit placed 12 km away (fixed).** `ue_apply_facade_kit` used the static-mesh bounding box, which is building-line-local. Now uses `get_actor_bounds()` (world space). - -### Majors - -- **Window bays were inside the solid wall (fixed).** The first piece modelled the whole bay at +Y, so every window sat inside the blockout volume and was invisible. The surround now projects 3 cm with glazing 6 cm behind its face. -- **Kit had no materials (fixed).** Pieces kept the grey `M_Kit_*` material the FBX import creates, so the glazing was not glass and the shopfronts were not stone. Assembly now assigns `M_FacadeGlass` to glazing, `M_FacadeStone` to surrounds, `M_FacadeStoneShadow` to doors and `M_FacadeBanner` to fascias. - -### Minors - -- Kit pieces are boxes: no reveals, sills or mouldings beyond the kit's own bands. Acceptable at this patch's fidelity; the kit is the vocabulary, not the detail. -- `M_Kit_*` materials are deleted at import rather than reused, so the kit's Blender materials carry no shading intent — the Unreal set is authoritative. Documented, not silent. -- Actor labels for kit pieces are unique per building but long (`Kit_elcapitantheatre_window_3_2`). - -### Notes (polish, not required for `v0.1.4`) - -- Hero fluting, and the motto band/medallions as geometry, are still pending. -- The El Capitan Theatre has no marquee or blade sign yet — the features that make it read as itself. -- Cornice dentil spacing, abacus and plinth dimensions remain recorded assumptions. - -## Not verified here - -- **Reference match.** Needs a human comparing `Screenshots/demo/*` against `Art/reference/hollywood/`. The vision pre-check is demonstrably unreliable on this content (it rejected shots the owner has since approved), so it was not used as evidence. -- **Silhouette and shading at street level** — the same review covers it. - -## Next actions - -1. Owner review of the 14 demo/review shots; record approval in `Screenshots/README.md`. -2. Then the remaining `0.1.4` modelling: theatre marquee + blade sign, hero fluting/motto band, blockout cleaning pass. -3. `CHANGELOG.md` entry and `v0.1.4` tag once (1) is signed off. diff --git a/Docs/reviews/0.1.4-review.md b/Docs/reviews/0.1.4-review.md index 0e33c9c..20f4e04 100644 --- a/Docs/reviews/0.1.4-review.md +++ b/Docs/reviews/0.1.4-review.md @@ -1,8 +1,8 @@ # `0.1.4` façade review — signoff -Four demo-map captures from the `facades-0.1.4` branch, after the module kit, the textured materials and the theatre marquee went in. Tick each row you accept; anything not ticked is still open. Mechanical checks (blank/size/tone) already pass via `python3 Tools/visual_loop.py --verify`. +Four demo-map captures from the `facades-0.1.4` branch, after the module kit, the textured materials and the theatre marquee went in. **All seven items signed off 2026-11-09** (owner review of the local renders). -Reference photographs to compare against: `Art/reference/hollywood/` and `Art/reference/el-capitan-*` (local, not committed). Regenerate the set with `Tools/render_screenshots.sh --verify`. +Reference photographs to compare against: `Art/reference/hollywood/` and `Art/reference/el-capitan-*` (local, not committed). Regenerate the set with `Tools/render_screenshots.sh --verify`; measure it with `python3 Tools/visual_loop.py --compare --iteration 5`. ## Signoff @@ -40,4 +40,41 @@ Expected: TCL Chinese Theatre and Madame Tussaud's across Hollywood Blvd, full h --- -**Notes for the decision:** every defect found in this cycle that survived more than one render was a placement error, not a shading one (a 119 m frame shift, a kit 12 km off, a marquee mirrored across the street). Placement is now probed rather than eyeballed, so the remaining risk in these four images is _likeness_, not correctness. The low edge ratios are partly structural — a wide block view compared with a tight façade crop always scores low — so weigh the images, not the scores. +## Appendix — what was checked, and what was broken + +### Verified + +- **Behaviour**, packaged build, `-qaselftest`: walk 761 cm in 2 s (Z steady 456), reset 0 cm, pause/unpause, seam transect 1456 samples / **0 holes**, wall probe **0/35**, façade occlusion probe **0/9** — all PASS. Run twice: the harness is time-based and one loaded run reported a false failure (`walk 246 cm`, `pause FAIL`), an immediate re-run passed — re-run before believing a single FAIL. +- **Placement**, probed from the live map against `Geo/verification/2026-11-06-el-capitan-block.md`: 6922 −24276…−17700, 6902 −17749…−14591, **hero −14644…−12028 between them**, theatre −12082…−8210, 6822 / 6806 / 6800 −7854…−1437, TCL −22889…−18442, Tussaud's −27071…−22799. Blockout affine residual **0.0 cm**. +- **Budgets**: hero 148 parts / 3 044 tris · kit 16 meshes × 12 tris · 625 kit actors ≈ 7 500 tris · 8 × 512² textures (196 KB) · 67 tool tests pass. +- **Topology**: every kit piece manifold, no loose geometry. + +### Defects found this cycle + +Every one of these rendered convincingly; the placement ones were caught by coordinate probes, not by looking. + +| Sev | Defect | Root cause | +| --- | --- | --- | +| Blocker | Frontage shifted ≈119 m X / 2.4 m Y, hero outside its gap | Blockout regenerated without `--origin` | +| Blocker | Kit placed ~12 km from its buildings | Used mesh-local bbox; actors carry the offset | +| Blocker | Marquee on the far side of the boulevard | Blender +Y is Unreal −Y | +| Major | Marquee canopy 5 cm above the pavement | Absolute Z; pavement is 4.56 m above datum | +| Major | Window bays invisible, inside the solid wall | Piece modelled entirely at +Y | +| Major | Glazing wasn't glass | Grey `M_Kit_*` from the FBX import, never overridden | +| Major | Hero windows floated 1.44 m in front of the wall | Wrong Y in the builder | +| Major | Parapet medallions were discs on the coping | Built with the Z-axis cylinder helper | +| Major | Hero had no usable collision | Complex-only; traces use simple | +| Minor | "No smoothing group information" on every import | `mesh_smooth_type="OFF"` in six exporters | +| Minor | `save_map()` returned False silently | LFS `.umap` read-only, importer never chmod'd | +| Minor | North-buildings import silently no-op | Missing `import sys` | +| Minor | Kit folder held 24 assets (8 orphan materials) | No stale-asset sweep | +| Minor | Edge-density metric counted PIL's border ring | `FIND_EDGES` leaves a 128 ring | +| Minor | QA wall probe grazed the theatre wall | Scan row 1 cm from the building line | + +### Limitations worth knowing + +- Kit pieces are boxes; dentil spacing, abacus and plinth dimensions are recorded assumptions, not measurements. +- Textures are generated, not authored — no decals, signage text or weathering. +- The vision pre-check stays advisory: it has rejected shots the owner approved. +- The low edge ratios in the metrics are partly structural — a wide block view against a tight façade crop always scores low. Weigh the images, not the scores. +- Performance has not been re-measured since `0.1.0`; the 625 kit actors are unmeasured (tracked in `TODO.md` under `0.1.5`). -- 2.47.3