From bc5da3a8ab4dc5c159c19c9f1c7fb74ec31a68c0 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 22:05:08 +0100 Subject: [PATCH 1/5] fix(kit): cap bay count and fill the storey spread; fetch the retail reference Kit layout, found while starting the retail pass: - Bays were round(width / 4.96), so 6922 (65.8 m wide) got 13 bays - a window every 4.96 m across a 37 m block. Capped at MAX_BAYS = 9, widening the module instead. - Storeys were assumed exactly 310 cm apart, leaving ~1.6 m of bare wall under the cornice on a tall building. The spread is now derived from the available height (usable / storeys), so the top band lands inside the volume. Result: 6922 9x11 (was 13x11), theatre 8x6, 6806 7x3, 6800 4x3, TCL 9x5. Retail references: fetched Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg (Gb321, CC BY-SA 4.0) - 6800 and its neighbour at street level, confirmed to show shopfronts and parapets. Commons has nothing for 6822 or 6806-6820, so those two remain blocked on reference photography; recorded in TODO. Re-applied the kit, repackaged, screenshots re-rendered and verified 4/4, packaged QA all PASS. --- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 ++-- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 16 +++++++++++++++ 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/ue_apply_facade_kit.py | 20 +++++++++++++++++-- 9 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 2b1a82e..9fb60f0 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:9ca76190a48710324d82892f5f56e423f123156f1b8e83740e37dbe3f7aa418d -size 958399 +oid sha256:1dadfbce54000421368ccbcb76cf04a8799c314f54c124bc7eb92fc23bbba754 +size 919659 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 562ce47..0ae6bb6 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:3d7897fdd23c11963990f71530a4630f8e2067503498c5906b13e232a79819e7 +oid sha256:872bca7e7dd0fdb41b213f0fbcbba048041311e30db43b60bd4d5e24d6f1db29 size 28811 diff --git a/MEMORY.md b/MEMORY.md index 2df3716..0063b91 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -436,3 +436,19 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. scope is cut deliberately and recorded in `CHANGELOG.md`, never the frame rate. Walkability outranks realism in one direction: a block the player falls through is not a demo. + +## 2026-11-09 — 0.1.5 start: retail reference + kit layout maths + +- Commons holds exactly **one** usable reference for the retail run: + `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA + 4.0, 1920×953) — 6800 Lee Drug **and** its neighbour at street level, shopfronts + and parapets visible (vision pre-check: yes). Nothing for 6822 or 6806–6820, so + those two façades still have no reference and stay blocked. +- **Kit layout was wrong for tall/wide buildings**, not just ugly: bays were + `round(width / 496)`, so 6922 (65.8 m wide, 37.1 m tall) got **13 bays** — a + window every 4.96 m across the whole frontage — and the storey count came out at + 11 × 310 cm = 34.1 m against a 35.7 m window zone, leaving bare wall under the + cornice. Fixed: `MAX_BAYS = 9` (module widens instead of subdividing), and the + storey spread is derived from the available height (`usable / storeys`) so the top + band always lands inside the building. Result: 6922 9 × 11, theatre 8 × 6, + 6806 7 × 3, 6800 4 × 3, TCL 9 × 5. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 6a19fc7..111f265 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:9cd5184c2c1f932e5262322c6ee21d9fae24f4af534ed6b641c11e0cd722952b -size 3079101 +oid sha256:11730e81d292ccba5357565a6315bd7f5b475ff4713813e22ea2dfd880e4c869 +size 3080043 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index de0eca2..2fb99bc 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:153b25b43f293ebf793fa8d0d685b0ef0be2a94267a03937180e4bcd602ed561 -size 2442501 +oid sha256:35b9ecf5f6fa1431777fc78f46ed7ab050e8d86da34a63024e741efc99b0557b +size 2419489 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index da3f6bf..62dcc78 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:9feb51f299584f7c7e76e69bc1e05e381b9cad381e5b0375e3430c1ea5fcaac6 -size 2946077 +oid sha256:a40c52112be8d72b9d5f85b66434fed255399868b35d9c86f89c909b56dea794 +size 2949229 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 77452f8..045bd58 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:2fdec8a67016806bbff3591f449bc16c19df35df2ae98cb18c38c15d0b91a6a5 -size 2865770 +oid sha256:33b9bd8fcb4a7aa2b6cc916688769590fb7e314e37d0ab81c0c24a565dec4e1f +size 2867127 diff --git a/TODO.md b/TODO.md index 00f551b..860b2cf 100644 --- a/TODO.md +++ b/TODO.md @@ -17,7 +17,7 @@ The `0.1.4` kit, materials and theatre trim are signed off; these are the geomet ### 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. +- [A] **Retail neighbours (6822 Budget, 6806–6820, 6800 Lee Drug) from their own references.** **Started 2026-11-09.** Fetched the one usable reference Commons holds for this run — `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA 4.0, 1920×953) — which shows 6800 **and** its neighbour at street level; the vision pre-check confirmed shopfronts and parapets are visible. The 18-frame theatre set cannot stand in for these façades, and Commons has nothing else for 6822 or 6806–6820, so per-building references for those two remain the blocker. **Kit layout fixed while here:** bay count was `round(width / 4.96)`, which gave 6922 thirteen bays across 65.8 m (a window every 4.96 m on a 37 m block) and left bare wall under the cornice; bays are now capped at 9 with the module widened, and the storey spread fills the available height (6922 9 × 11, 6806 7 × 3, 6800 4 × 3, theatre 8 × 6). - [ ] **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. diff --git a/Tools/ue_apply_facade_kit.py b/Tools/ue_apply_facade_kit.py index 5606b91..d3768a5 100644 --- a/Tools/ue_apply_facade_kit.py +++ b/Tools/ue_apply_facade_kit.py @@ -123,12 +123,28 @@ def apply_to_actor(actor, kit, materials=None, prefix=None): f"({width / 100:.1f} x {height / 100:.1f} m)") return 0 + # Bays: the kit module is one window bay (4.96 m). A very wide building gets a + # bay count that keeps the rhythm readable rather than one window per 4.96 m of + # frontage — 6922 is 65.8 m wide, which is 13 bays, giving a window every 4.96 m + # across a 37 m tall block: the rhythm reads as a wall of glass, not a building. + # Above MAX_BAYS the module is widened instead, so the bay count stays legible. + MAX_BAYS = 9 bays = max(1, int(round(width / BAY_CM))) + if bays > MAX_BAYS: + bays = MAX_BAYS 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)) + # Storeys never overrun the volume: cap the count so the top band (cornice + + # parapet, 145 cm) always lands inside the building. + storeys = max(1, int((height - SHOP_FLOOR_CM - 145.0) // STOREY_CM) + 1) + top_z = base_z + height + # Windows above the shopfront, spread over the available height rather than + # assumed to be exactly STOREY_CM apart, so a 37 m block does not leave a gap + # of bare wall under the cornice. + usable = max(0.0, (top_z - 145.0) - (base_z + SHOP_FLOOR_CM)) + storey_step = (usable / storeys) if storeys else STOREY_CM def kit_material(name): if materials is None: @@ -153,7 +169,7 @@ def apply_to_actor(actor, kit, materials=None, prefix=None): # 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 + z = base_z + SHOP_FLOOR_CM + storey_step * 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 -- 2.47.3 From 92b477d34011a556f504dd479ab154db5b5e4d9a Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 22:12:44 +0100 Subject: [PATCH 2/5] feat(hero): fluting and a recessed motto band; placement gets one source of truth Hero detail: - Fluting: 20 grooves per column as thin shadow-tone cylinders set into the shaft (120 pieces) rather than boolean cuts - readable in silhouette and grazing light at a fraction of the cost. - Motto band: real recess with a moulded surround 6 cm proud and the panel 12 cm behind, replacing the 8 cm dark stripe. Hero 148 parts/3044 tris -> 271/5492, still 14.50 m. Placement bug this exposed (a real one, invisible in a render): - ue_hero_facade_import.py carried its own hero position (-12024.5, -1450) from before the 0.1.0 frontage fix, so running the importer alone moved the hero 13 m east and onto the north side of the boulevard. Only symptom: the reference score fell 0.69 -> 0.56 and the 3x3 composition correlation flipped +0.54 -> -0.08. - New Tools/ue_placement.py is the single source of truth (hero position, verified frontage spans, span validators). Both the importer and the demo builder use it; the import asserts the staged span and logs PLACEMENT ERROR otherwise. - 11 new tests, including one that fails if the stale literal returns. 78 total. Spawn-view score recovered to 0.72 (best yet); screenshots verified 4/4, packaged QA all PASS. --- Art/blender/hero/hero-facade-2026-11-07.blend | 4 +- Art/blender/hero/hero-facade-2026-11-07.json | 13 ++- Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Hollywood/ElCapitan/SM_HeroFacade.uasset | 4 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 22 +++++ 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 | 1 + Tools/hero_facade_build.py | 37 +++++++- Tools/tests/test_ue_placement.py | 86 +++++++++++++++++++ Tools/ue_build_demo_map.py | 4 +- Tools/ue_hero_facade_import.py | 20 ++++- Tools/ue_placement.py | 65 ++++++++++++++ 17 files changed, 255 insertions(+), 25 deletions(-) create mode 100644 Tools/tests/test_ue_placement.py create mode 100644 Tools/ue_placement.py diff --git a/Art/blender/hero/hero-facade-2026-11-07.blend b/Art/blender/hero/hero-facade-2026-11-07.blend index 8d91f5a..0147c6f 100644 --- a/Art/blender/hero/hero-facade-2026-11-07.blend +++ b/Art/blender/hero/hero-facade-2026-11-07.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cadc2469e3e9fb41dc379a1c61489054533819478fa74befecaa37adcfdadd97 -size 158985 +oid sha256:22d94a59b1ee5d77a72ee2b2096a0a73de3fc594a6e7d80cd46fbb05faa71e9e +size 208756 diff --git a/Art/blender/hero/hero-facade-2026-11-07.json b/Art/blender/hero/hero-facade-2026-11-07.json index 00d70b6..12420c1 100644 --- a/Art/blender/hero/hero-facade-2026-11-07.json +++ b/Art/blender/hero/hero-facade-2026-11-07.json @@ -37,6 +37,15 @@ ], "evidence": "6 columns at 4.96 m centres (sibling notes x measured frontage)" }, + { + "object": "Hero_Flute_x120", + "dims_m": [ + 0.064, + 0.064, + 6.53 + ], + "evidence": "photographs: fluted shafts; 20 grooves per column modelled as shallow cylinders in the shadow tone rather than boolean cut grooves" + }, { "object": "Hero_ColumnBase_x6", "dims_m": [ @@ -104,10 +113,10 @@ "object": "Hero_MottoBand", "dims_m": [ 22.75, - 0.08, + 0.1, 0.85 ], - "evidence": "photographs: motto panel above the colonnade" + "evidence": "photographs: motto panel above the colonnade, now an inset panel with a moulded surround rather than a flat stripe" }, { "object": "Hero_Medallion_x2", diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 9fb60f0..686adbe 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:1dadfbce54000421368ccbcb76cf04a8799c314f54c124bc7eb92fc23bbba754 -size 919659 +oid sha256:1c4b312ef29760c4a167d97397c18d691d2060291b8f2bd1876a590b2b38a214 +size 919602 diff --git a/Game/GigLine/Content/Hollywood/ElCapitan/SM_HeroFacade.uasset b/Game/GigLine/Content/Hollywood/ElCapitan/SM_HeroFacade.uasset index a0e205b..a318e07 100644 --- a/Game/GigLine/Content/Hollywood/ElCapitan/SM_HeroFacade.uasset +++ b/Game/GigLine/Content/Hollywood/ElCapitan/SM_HeroFacade.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b506386f8694a3788ec68d0f1feb9b4a6fa604282014d593514e3cefaa247f4 -size 142674 +oid sha256:ab0c6c8ddbc2a9086be3c474166b7fe152f2e0a0aa88cf55500feec21a4e10cf +size 186727 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 0ae6bb6..8432086 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:872bca7e7dd0fdb41b213f0fbcbba048041311e30db43b60bd4d5e24d6f1db29 +oid sha256:24610a4a1230a866990aaff5491a77a56250f347773dd3130615b76b53713e4f size 28811 diff --git a/MEMORY.md b/MEMORY.md index 0063b91..1f793d1 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -452,3 +452,25 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. storey spread is derived from the available height (`usable / storeys`) so the top band always lands inside the building. Result: 6922 9 × 11, theatre 8 × 6, 6806 7 × 3, 6800 4 × 3, TCL 9 × 5. + +## 2026-11-09 — hero fluting + motto band; and a silent placement divergence + +- Fluting: 20 grooves per column as thin shadow-tone cylinders set into the shaft + (120 pieces) rather than boolean cuts — the eye reads them in silhouette and + grazing light, booleans would cost far more for that. Motto band is now a real + recess: surround 6 cm proud, panel 12 cm behind (was an 8 cm dark stripe). + Hero 148 parts/3 044 tris -> 271/5 492, still 14.50 m. +- **The real find:** `ue_hero_facade_import.py` had its own hero position + `(-12024.5, -1450, 0)` — the pre-`0.1.0` value, before the frontage placement fix + moved the hero to `(-13336, +1307)`. Running the importer on its own therefore + teleported the hero **13 m east and onto the north side of the boulevard**, and + nothing complained: a displaced building still renders. The only symptom was the + reference score dropping 0.69 -> 0.56 with the 3x3 composition correlation + flipping +0.54 -> -0.08. +- Fix: `Tools/ue_placement.py` is now the single source of truth (hero position, + verified frontage spans, `span_error`/`describe_span_failure`). Both + `ue_hero_facade_import.py` and `ue_build_demo_map.py` import it, the import asserts + the staged span and logs `PLACEMENT ERROR` when it does not match, and tests assert + the stale literal cannot come back. Spawn-view score back to **0.72** (best yet). +- Lesson to keep: **duplicated placement constants drift silently.** The metric that + caught it was the composition correlation, not a visual review. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index 111f265..b49e26e 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:11730e81d292ccba5357565a6315bd7f5b475ff4713813e22ea2dfd880e4c869 -size 3080043 +oid sha256:c7729a7ef0b10c5827f3c5b4167e12ba03ca6aa81a8a1da0c55f9fb122897694 +size 2992299 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 2fb99bc..c655726 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:35b9ecf5f6fa1431777fc78f46ed7ab050e8d86da34a63024e741efc99b0557b -size 2419489 +oid sha256:a854cc82d6a1ad84173664076a076f649c8dfbf38bb7d887f7d6444a7dc4e57c +size 2417601 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 62dcc78..f868673 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:a40c52112be8d72b9d5f85b66434fed255399868b35d9c86f89c909b56dea794 -size 2949229 +oid sha256:e06925a4ca759e762c3ad01375cc098e22fdab065878a85269a63d62b9ba7256 +size 2505879 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index 045bd58..21ad29f 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:33b9bd8fcb4a7aa2b6cc916688769590fb7e314e37d0ab81c0c24a565dec4e1f -size 2867127 +oid sha256:e0412f034f6ac55928521c14278043f5626ce32ecaaca7e178ec3efd011efc22 +size 2705233 diff --git a/TODO.md b/TODO.md index 860b2cf..77052c7 100644 --- a/TODO.md +++ b/TODO.md @@ -18,7 +18,7 @@ The `0.1.4` kit, materials and theatre trim are signed off; these are the geomet ### Patch tasks - [A] **Retail neighbours (6822 Budget, 6806–6820, 6800 Lee Drug) from their own references.** **Started 2026-11-09.** Fetched the one usable reference Commons holds for this run — `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA 4.0, 1920×953) — which shows 6800 **and** its neighbour at street level; the vision pre-check confirmed shopfronts and parapets are visible. The 18-frame theatre set cannot stand in for these façades, and Commons has nothing else for 6822 or 6806–6820, so per-building references for those two remain the blocker. **Kit layout fixed while here:** bay count was `round(width / 4.96)`, which gave 6922 thirteen bays across 65.8 m (a window every 4.96 m on a 37 m block) and left bare wall under the cornice; bays are now capped at 9 with the module widened, and the storey spread fills the available height (6922 9 × 11, 6806 7 × 3, 6800 4 × 3, theatre 8 × 6). -- [ ] **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. +- [x] **Hero façade: fluting and the motto band as geometry.** **Done 2026-11-09.** Fluting: 20 grooves per column (thin shadow-tone cylinders set into the shaft, 120 pieces) instead of boolean cuts — it breaks the flat highlight at a fraction of the poly cost. Motto band: a real recess now, with a moulded surround standing 6 cm proud and the panel set 12 cm behind it, replacing the 8 cm dark stripe. Hero is 271 objects / 5 492 tris (from 148 / 3 044) and still 14.50 m. **En route it exposed a placement bug:** `ue_hero_facade_import.py` carried its own stale hero position `(-12024.5, -1450)` from before the `0.1.0` frontage fix, so running the importer alone moved the hero 13 m east and onto the north side — invisible in a render, and it dropped the reference score from 0.69 to 0.56. Placement now lives in `Tools/ue_placement.py` with span validators, asserted after every import (`span ok: X -14644..-12028`), and the spawn-view score recovered to **0.72**. - [ ] **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. diff --git a/Tools/README.md b/Tools/README.md index 2ef7335..3d582e0 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -22,6 +22,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | `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_placement.py` | Single source of truth for where staged actors belong in the DemoMap (hero position, verified frontage spans) plus `span_error`/`describe_span_failure` validators. Exists because the hero position was written twice and drifted — the importer kept the pre-`0.1.0` value and moved the hero 13 m east onto the wrong side of the boulevard. No `unreal` import, so it is unit-tested. | | `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/hero_facade_build.py b/Tools/hero_facade_build.py index 964a419..415f453 100644 --- a/Tools/hero_facade_build.py +++ b/Tools/hero_facade_build.py @@ -174,6 +174,20 @@ def build(): cap_z0 = HEIGHT - ENTABLATURE_H - PARAPET_H - CAPITAL_H cylinder(f"Hero_Column_{i + 1}", x, -0.55, shaft_z0, cap_z0, COLUMN_D / 2.0, stone) + + # Fluting: 20 shallow grooves around the shaft, as the photographs show on + # the colonnade. Each flute is a thin cylinder set into the shaft surface; + # modelling them as grooves would need booleans on 6 columns for detail the + # eye reads only in silhouette and grazing light, so they are added mass in + # the *shadow* direction instead — cheap, and it breaks the flat highlight. + flutes = 20 + flute_r = 0.032 + for f in range(flutes): + ang = (2.0 * math.pi * f) / flutes + fx = x + (COLUMN_D / 2.0 - flute_r * 0.4) * math.cos(ang) + fy = -0.55 + (COLUMN_D / 2.0 - flute_r * 0.4) * math.sin(ang) + cylinder(f"Hero_Flute_{i + 1}_{f + 1}", fx, fy, shaft_z0 + 0.05, + cap_z0 - 0.05, flute_r, stone_dark, vertices=6) # Capital block, then the square abacus that carries the architrave. # The abacus takes the top 0.14 m of the capital zone so the colonnade # still ends at HEIGHT - ENTABLATURE_H - PARAPET_H (proportion unchanged). @@ -185,6 +199,9 @@ def build(): 1.30, 1.30, abacus_h, stone) note("Hero_Column_x6", (COLUMN_D, COLUMN_D, HEIGHT - ENTABLATURE_H - PARAPET_H - PEDESTAL_H), f"6 columns at {COLUMN_BAY} m centres (sibling notes x measured frontage)") + note("Hero_Flute_x120", (0.064, 0.064, cap_z0 - shaft_z0), + "photographs: fluted shafts; 20 grooves per column modelled as shallow " + "cylinders in the shadow tone rather than boolean cut grooves") note("Hero_ColumnBase_x6", (1.36, 1.36, 0.22), "assumption: Ionic base moulding; photographs show a wider base at the shaft foot") note("Hero_Abacus_x6", (1.30, 1.30, 0.14), @@ -223,10 +240,22 @@ def build(): FRONTAGE_W, 1.2, PARAPET_H, stone) note("Hero_Parapet", (FRONTAGE_W, 1.2, PARAPET_H), "assumption: parapet carries the motto band") - # Motto band: a recessed panel across the parapet, as the photographs show. - cube("Hero_MottoBand", 0.0, -1.32, HEIGHT - PARAPET_H / 2.0, - FRONTAGE_W - 3.0, 0.08, 0.85, stone_dark) - note("Hero_MottoBand", (FRONTAGE_W - 3.0, 0.08, 0.85), "photographs: motto panel above the colonnade") + # Motto band: a recessed panel across the parapet with a moulded surround, as + # the photographs show. The panel was a 8 cm stripe of dark stone; it is now a + # real recess — surround frame standing 6 cm proud, panel set 12 cm behind it — + # so it reads as an inscription panel from the street instead of a paint line. + motto_z = HEIGHT - PARAPET_H / 2.0 + motto_w = FRONTAGE_W - 3.0 + for x_off in (-(motto_w / 2.0 + 0.16), (motto_w / 2.0 + 0.16)): + cube(f"Hero_MottoSurround_v{int(x_off > 0)}", x_off, -1.13, motto_z, + 0.32, 0.26, 1.15, stone) + for z_off in (-0.575, 0.575): + cube(f"Hero_MottoSurround_h{int(z_off > 0)}", 0.0, -1.13, motto_z + z_off, + motto_w + 0.64, 0.26, 0.32, stone) + cube("Hero_MottoBand", 0.0, -1.06, motto_z, motto_w, 0.10, 0.85, stone_dark) + note("Hero_MottoBand", (motto_w, 0.10, 0.85), + "photographs: motto panel above the colonnade, now an inset panel with a " + "moulded surround rather than a flat stripe") # Medallions either side of the inscription: discs on the parapet face, not # pancakes on the coping (see disc_on_wall). diff --git a/Tools/tests/test_ue_placement.py b/Tools/tests/test_ue_placement.py new file mode 100644 index 0000000..6835a51 --- /dev/null +++ b/Tools/tests/test_ue_placement.py @@ -0,0 +1,86 @@ +"""Tests for Tools/ue_placement.py — the single source of truth for placement. + +A regression test for a real bug: `ue_hero_facade_import.py` carried its own hero +position (the pre-0.1.0 one) while `ue_build_demo_map.py` staged the verified one. +Running the importer moved the hero 13 m east and onto the north side of the +boulevard, and nothing complained because a displaced building still renders. +""" + +import os +import sys +import unittest + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +sys.path.insert(0, os.path.join(ROOT, "Tools")) + +import ue_placement # noqa: E402 + + +class HeroPlacementTest(unittest.TestCase): + def test_hero_actor_name(self): + self.assertEqual(ue_placement.HERO_ACTOR, "Hero_Facade") + + def test_hero_location_is_the_verified_building_line(self): + x, y, z = ue_placement.HERO_LOCATION_CM + self.assertEqual((x, y, z), (-13336.0, 1307.0, 0.0)) + # North is -Y in Unreal, so the hero on the south side must have Y > 0. + self.assertGreater(y, 0.0, "hero must be on the south side (Y > 0)") + + def test_hero_span_is_verified(self): + # The mesh spans -1308..+1308 cm in X about its origin. + lo = ue_placement.HERO_LOCATION_CM[0] - 1308.0 + hi = ue_placement.HERO_LOCATION_CM[0] + 1308.0 + exp_min, exp_max, ok = ue_placement.span_error("hero", lo, hi) + self.assertTrue(ok) + # The mesh spans exactly the verified footprint, so the two agree. + self.assertAlmostEqual(exp_min, lo, delta=ue_placement.SPAN_TOLERANCE_CM) + self.assertAlmostEqual(exp_max, hi, delta=ue_placement.SPAN_TOLERANCE_CM) + + +class SpanValidatorTest(unittest.TestCase): + def test_misplaced_hero_is_reported(self): + # The exact failure observed: the stale import position put the hero here. + lo, hi = -13332.0, -10716.0 + exp_min, exp_max, ok = ue_placement.span_error("hero", lo, hi) + self.assertFalse(ok) + message = ue_placement.describe_span_failure("hero", lo, hi) + self.assertIn("does not match the verified", message) + self.assertIn(f"{exp_min:.0f}", message) + + def test_correct_span_reports_nothing(self): + self.assertEqual(ue_placement.describe_span_failure("hero", -14644, -12028), "") + + def test_within_tolerance_passes(self): + # 99 cm off is inside the ±100 cm tolerance. + self.assertTrue(ue_placement.span_error("6922", -24276 + 99, -17700 - 99)[2]) + + def test_outside_tolerance_fails(self): + self.assertFalse(ue_placement.span_error("6922", -24276 + 101, -17700)[2]) + + def test_unknown_building_raises(self): + with self.assertRaises(KeyError): + ue_placement.span_error("no-such-building", 0, 0) + + def test_every_frontage_span_is_ordered(self): + for name, (lo, hi) in ue_placement.FRONTAGE_SPANS_CM.items(): + self.assertLess(lo, hi, f"{name} span is inverted") + + +class SingleSourceOfTruthTest(unittest.TestCase): + """The import script and the demo builder must not carry their own literals.""" + + def test_import_script_uses_the_shared_constant(self): + src = open(os.path.join(ROOT, "Tools", "ue_hero_facade_import.py"), + encoding="utf-8").read() + self.assertIn("ue_placement.HERO_LOCATION_CM", src) + self.assertNotIn("(-12024.5, -1450.0, 0.0)", src, + "the stale pre-0.1.0 hero position must not come back") + + def test_demo_builder_uses_the_shared_constant(self): + src = open(os.path.join(ROOT, "Tools", "ue_build_demo_map.py"), + encoding="utf-8").read() + self.assertIn("ue_placement.HERO_LOCATION_CM", src) + + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 33e5f2b..f125935 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -23,6 +23,8 @@ 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__), "..")) +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import ue_placement # noqa: E402 RESULT = "/tmp/gigline/demo-map.txt" lines = [] @@ -401,7 +403,7 @@ def build(): # the NORTH side of the street, which only looked right while it stood alone. hero = unreal.EditorAssetLibrary.load_asset("/Game/Hollywood/ElCapitan/SM_HeroFacade") if hero: - actor = spawn(unreal.StaticMeshActor, unreal.Vector(-13336.0, 1307.0, 0.0), "Hero_Facade") + actor = spawn(unreal.StaticMeshActor, unreal.Vector(*ue_placement.HERO_LOCATION_CM), ue_placement.HERO_ACTOR) if actor: actor.static_mesh_component.set_static_mesh(hero) actor.static_mesh_component.set_mobility(unreal.ComponentMobility.STATIC) diff --git a/Tools/ue_hero_facade_import.py b/Tools/ue_hero_facade_import.py index 49e4e8b..46f0559 100644 --- a/Tools/ue_hero_facade_import.py +++ b/Tools/ue_hero_facade_import.py @@ -19,14 +19,19 @@ import unreal sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import ue_common # noqa: E402 +import ue_placement # noqa: E402 FBX = "/tmp/gigline/hero-facade.fbx" DEST = "/Game/Hollywood/ElCapitan" -ACTOR_LABEL = "Hero_Facade" +ACTOR_LABEL = ue_placement.HERO_ACTOR LEVEL = "/Game/Hollywood/DemoMap" # Unreal centimetres: south-east corner of the façade, on the building line. -PLACEMENT_CM = (-12024.5, -1450.0, 0.0) +# Placement lives in Tools/ue_placement.py. It used to be a literal here, and it +# drifted from ue_build_demo_map.py: this file kept the pre-0.1.0 position +# (-12024.5, -1450) while the builder staged (-13336, 1307), so running the importer +# on its own moved the hero 13 m east and onto the north side of the boulevard. +PLACEMENT_CM = ue_placement.HERO_LOCATION_CM RESULT = "/tmp/gigline/hero-facade-import.txt" lines = [] @@ -118,6 +123,17 @@ def measure_and_place(): comp.set_static_mesh(asset) comp.set_mobility(unreal.ComponentMobility.STATIC) log(f"placed {ACTOR_LABEL} at {PLACEMENT_CM} (cm) on {LEVEL}") + + # Prove it landed on its verified footprint, not merely that it spawned. + box = asset.get_bounding_box() if asset else None + if box: + lo = box.min.x + PLACEMENT_CM[0] + hi = box.max.x + PLACEMENT_CM[0] + problem = ue_placement.describe_span_failure("hero", lo, hi) + if problem: + log(f"PLACEMENT ERROR: {problem}") + else: + log(f"span ok: X {lo:.0f}..{hi:.0f}") _make_map_writable(LEVEL) saved = unreal.EditorLoadingAndSavingUtils.save_map( unreal.EditorLevelLibrary.get_editor_world(), LEVEL) diff --git a/Tools/ue_placement.py b/Tools/ue_placement.py new file mode 100644 index 0000000..9d20228 --- /dev/null +++ b/Tools/ue_placement.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Single source of truth for where staged actors belong in the DemoMap. + +This exists because the same value was written in two places and they drifted: +`ue_hero_facade_import.py` kept the pre-`0.1.0` position `(-12024.5, -1450)` while +`ue_build_demo_map.py` staged the hero at `(-13336, 1307)`. Running the importer on +its own therefore moved the hero **13 m east and onto the north side of the +boulevard** — and nothing complained, because a displaced building still renders. + +The values are Unreal centimetres, absolute world space. Every one of them is +verified against `Geo/verification/2026-11-06-el-capitan-block.md` and probed by +`Tools/visual_loop.py --verify` (via the demo screenshot expectations) and by the +world-span probes recorded in `MEMORY.md`. + +Keep this file boring: constants and one validator, no `unreal` import, so it can +be unit-tested on the host. +""" + +from __future__ import annotations + +# The hero façade sits INSIDE the frontage gap between 6902 (ends at -17749) and +# the El Capitan Theatre (starts at -12082), so its actor origin is the measured +# building line. The mesh is authored with its front plane 7 cm ahead of the actor +# origin, which is why the X here is the frontage centre and Y is the building line. +HERO_ACTOR = "Hero_Facade" +HERO_LOCATION_CM = (-13336.0, 1307.0, 0.0) + +# Tolerance for the span probe: the verified footprints are within ~1 m. +SPAN_TOLERANCE_CM = 100.0 + +# Expected world span per frontage actor (min X, max X), from the block +# verification notes. Used to prove a staged building is on its own footprint +# rather than merely present. +FRONTAGE_SPANS_CM = { + "6922": (-24276.0, -17700.0), + "6902": (-17749.0, -14591.0), + "hero": (-14644.0, -12028.0), + "6838": (-12082.0, -8210.0), + "6822": (-7854.0, -7252.0), + "6806": (-6608.0, -3307.0), + "6800": (-3320.0, -1437.0), +} + + +def span_error(name: str, observed_min: float, observed_max: float): + """Return (expected_min, expected_max, ok) for an observed building span. + + `name` is a key of `FRONTAGE_SPANS_CM`; `ok` is False when either edge is more + than `SPAN_TOLERANCE_CM` from the verified value. + """ + if name not in FRONTAGE_SPANS_CM: + raise KeyError(f"unknown building {name!r}; known: {sorted(FRONTAGE_SPANS_CM)}") + exp_min, exp_max = FRONTAGE_SPANS_CM[name] + ok = (abs(observed_min - exp_min) <= SPAN_TOLERANCE_CM + and abs(observed_max - exp_max) <= SPAN_TOLERANCE_CM) + return exp_min, exp_max, ok + + +def describe_span_failure(name: str, observed_min: float, observed_max: float) -> str: + """A one-line message for a misplaced building, or "" when it is correct.""" + exp_min, exp_max, ok = span_error(name, observed_min, observed_max) + if ok: + return "" + return (f"{name}: span X {observed_min:.0f}..{observed_max:.0f} does not match the " + f"verified {exp_min:.0f}..{exp_max:.0f} (±{SPAN_TOLERANCE_CM:.0f} cm)") -- 2.47.3 From 442d0f3d73d72d05de040d8e4ce0aa66d7943229 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 22:38:04 +0100 Subject: [PATCH 3/5] perf: 1440p re-measured with the kit - 363 fps, ~12% cost Closes the 0.1.5 performance task. Same route and preset as 0.1.0 (packaged Development, Epic scalability, TSR, VSync off, no frame generation, 2560x1440): 0.1.0 2.42 ms (414 fps) p99 3.01 0.1.5 (kit) 2.76 ms (363 fps) p99 3.64 The 625 kit actors and 8 generated textures cost about 12% of frame time and still leave roughly six times the 60 fps floor. Frame-time series committed at Docs/perf/0.1.5-kit-walk-frametimes.csv; comparison table added to Docs/release-checks.md. Records the measurement trap once more: runs showing ~105 fps were two overlapping game instances, not the build - 363 fps reproduced twice on a clean single instance. --- Docs/perf/0.1.5-kit-walk-frametimes.csv | 728 ++++++++++++++++++++++++ Docs/release-checks.md | 16 + MEMORY.md | 13 + TODO.md | 2 +- 4 files changed, 758 insertions(+), 1 deletion(-) create mode 100644 Docs/perf/0.1.5-kit-walk-frametimes.csv diff --git a/Docs/perf/0.1.5-kit-walk-frametimes.csv b/Docs/perf/0.1.5-kit-walk-frametimes.csv new file mode 100644 index 0000000..1b6a112 --- /dev/null +++ b/Docs/perf/0.1.5-kit-walk-frametimes.csv @@ -0,0 +1,728 @@ +frame,ms +0,2.599 +1,2.877 +2,2.709 +3,2.646 +4,3.168 +5,2.729 +6,2.524 +7,2.726 +8,2.639 +9,2.814 +10,2.819 +11,2.708 +12,2.822 +13,2.703 +14,2.365 +15,2.796 +16,2.899 +17,2.972 +18,2.908 +19,2.356 +20,2.781 +21,2.574 +22,3.148 +23,2.306 +24,2.668 +25,2.855 +26,2.588 +27,3.321 +28,3.419 +29,3.121 +30,3.391 +31,2.355 +32,2.679 +33,3.463 +34,2.614 +35,2.922 +36,2.429 +37,2.830 +38,2.842 +39,2.782 +40,3.252 +41,2.731 +42,2.577 +43,2.363 +44,2.786 +45,2.547 +46,2.594 +47,2.652 +48,2.836 +49,2.792 +50,2.552 +51,2.792 +52,2.718 +53,3.333 +54,2.165 +55,2.880 +56,2.613 +57,2.460 +58,2.919 +59,2.770 +60,2.551 +61,2.622 +62,2.712 +63,2.818 +64,3.155 +65,2.472 +66,2.861 +67,2.651 +68,2.573 +69,2.892 +70,2.689 +71,2.729 +72,2.668 +73,2.753 +74,2.812 +75,2.606 +76,2.686 +77,2.842 +78,2.660 +79,2.903 +80,2.486 +81,2.591 +82,3.626 +83,2.455 +84,2.475 +85,2.922 +86,2.603 +87,2.728 +88,3.057 +89,2.731 +90,3.632 +91,1.950 +92,2.694 +93,3.020 +94,2.230 +95,2.796 +96,2.707 +97,2.807 +98,2.658 +99,2.697 +100,2.453 +101,3.051 +102,2.758 +103,2.662 +104,2.599 +105,2.733 +106,2.751 +107,2.894 +108,2.501 +109,2.753 +110,2.873 +111,2.439 +112,2.634 +113,2.682 +114,2.913 +115,2.692 +116,2.735 +117,2.758 +118,2.726 +119,2.907 +120,2.568 +121,2.764 +122,2.815 +123,2.530 +124,2.684 +125,2.719 +126,2.846 +127,2.879 +128,2.647 +129,2.770 +130,2.477 +131,2.657 +132,2.675 +133,2.917 +134,2.700 +135,2.493 +136,2.678 +137,2.786 +138,3.004 +139,2.697 +140,2.642 +141,2.624 +142,2.684 +143,2.717 +144,2.743 +145,2.740 +146,2.825 +147,2.849 +148,2.991 +149,2.297 +150,3.084 +151,2.459 +152,3.214 +153,2.260 +154,2.550 +155,2.621 +156,2.660 +157,2.739 +158,2.574 +159,3.078 +160,2.735 +161,2.607 +162,2.793 +163,2.798 +164,2.584 +165,2.637 +166,2.627 +167,2.726 +168,2.656 +169,2.788 +170,2.674 +171,2.782 +172,2.766 +173,2.613 +174,2.724 +175,2.770 +176,2.709 +177,2.700 +178,2.600 +179,2.686 +180,2.747 +181,2.841 +182,2.601 +183,2.666 +184,2.846 +185,2.599 +186,2.806 +187,3.126 +188,2.521 +189,2.634 +190,2.716 +191,2.584 +192,2.714 +193,2.923 +194,2.277 +195,2.706 +196,2.606 +197,2.758 +198,2.747 +199,2.958 +200,2.798 +201,2.387 +202,2.596 +203,2.861 +204,2.801 +205,2.521 +206,2.869 +207,2.884 +208,2.515 +209,2.592 +210,3.723 +211,2.474 +212,2.736 +213,2.774 +214,2.769 +215,3.007 +216,2.768 +217,2.716 +218,2.694 +219,2.722 +220,2.862 +221,2.587 +222,2.833 +223,2.792 +224,2.746 +225,2.585 +226,2.741 +227,2.585 +228,2.802 +229,2.674 +230,2.742 +231,2.718 +232,2.881 +233,2.707 +234,2.585 +235,2.810 +236,2.948 +237,2.718 +238,2.773 +239,2.571 +240,2.788 +241,2.702 +242,2.634 +243,2.788 +244,2.678 +245,2.914 +246,2.527 +247,2.637 +248,2.877 +249,2.693 +250,2.644 +251,2.670 +252,2.700 +253,2.877 +254,2.638 +255,2.617 +256,2.774 +257,2.660 +258,2.731 +259,2.671 +260,2.838 +261,2.726 +262,2.671 +263,2.744 +264,2.735 +265,2.684 +266,3.850 +267,2.120 +268,2.669 +269,2.729 +270,2.712 +271,2.817 +272,2.926 +273,2.678 +274,2.786 +275,2.927 +276,2.493 +277,2.846 +278,2.610 +279,2.644 +280,2.577 +281,2.939 +282,2.727 +283,2.595 +284,2.819 +285,2.778 +286,2.774 +287,2.628 +288,2.711 +289,2.699 +290,2.715 +291,2.763 +292,2.710 +293,2.892 +294,2.721 +295,2.575 +296,3.608 +297,2.811 +298,2.739 +299,2.751 +300,2.608 +301,2.913 +302,3.052 +303,2.862 +304,2.653 +305,2.727 +306,2.646 +307,2.888 +308,3.338 +309,2.743 +310,2.639 +311,2.681 +312,2.846 +313,2.766 +314,3.184 +315,2.834 +316,2.728 +317,2.581 +318,2.770 +319,3.351 +320,3.157 +321,2.508 +322,2.835 +323,2.754 +324,2.411 +325,3.398 +326,2.802 +327,2.611 +328,3.058 +329,2.407 +330,2.640 +331,3.645 +332,2.729 +333,2.580 +334,2.902 +335,2.523 +336,3.005 +337,3.173 +338,2.840 +339,2.610 +340,2.576 +341,2.706 +342,3.405 +343,2.849 +344,2.678 +345,2.796 +346,2.782 +347,2.636 +348,3.475 +349,2.798 +350,2.745 +351,2.564 +352,2.792 +353,2.714 +354,3.496 +355,2.841 +356,2.465 +357,2.708 +358,2.692 +359,2.934 +360,3.304 +361,2.561 +362,2.689 +363,2.749 +364,2.780 +365,2.940 +366,3.025 +367,2.706 +368,2.881 +369,2.619 +370,2.716 +371,3.133 +372,2.874 +373,2.816 +374,2.414 +375,2.737 +376,2.797 +377,2.651 +378,2.920 +379,3.032 +380,2.606 +381,2.710 +382,2.649 +383,2.700 +384,3.469 +385,2.224 +386,2.567 +387,2.733 +388,2.511 +389,2.950 +390,3.979 +391,2.798 +392,2.487 +393,2.740 +394,2.741 +395,2.718 +396,3.291 +397,2.769 +398,2.697 +399,2.796 +400,2.641 +401,2.652 +402,3.151 +403,2.673 +404,2.741 +405,2.606 +406,2.665 +407,2.859 +408,2.739 +409,2.586 +410,2.910 +411,2.745 +412,2.698 +413,2.670 +414,2.797 +415,2.829 +416,2.643 +417,2.776 +418,2.772 +419,2.530 +420,2.567 +421,2.826 +422,2.782 +423,2.738 +424,2.640 +425,2.967 +426,2.600 +427,3.020 +428,2.502 +429,2.642 +430,2.625 +431,2.770 +432,2.659 +433,2.717 +434,2.698 +435,2.780 +436,2.992 +437,2.686 +438,2.560 +439,3.048 +440,2.721 +441,2.507 +442,2.694 +443,2.695 +444,2.892 +445,3.443 +446,2.633 +447,2.908 +448,2.823 +449,2.346 +450,2.599 +451,2.914 +452,2.774 +453,3.391 +454,2.384 +455,2.585 +456,2.667 +457,2.704 +458,2.674 +459,2.755 +460,2.718 +461,2.804 +462,2.583 +463,2.694 +464,2.797 +465,3.079 +466,2.434 +467,2.723 +468,3.671 +469,2.211 +470,2.567 +471,2.701 +472,2.717 +473,2.657 +474,2.581 +475,2.889 +476,3.006 +477,2.595 +478,2.703 +479,2.498 +480,2.694 +481,2.915 +482,2.655 +483,2.686 +484,2.833 +485,2.717 +486,2.723 +487,2.451 +488,2.913 +489,2.784 +490,2.684 +491,2.651 +492,2.809 +493,2.819 +494,2.676 +495,2.851 +496,2.775 +497,2.638 +498,2.520 +499,2.824 +500,2.930 +501,2.757 +502,2.608 +503,2.808 +504,2.508 +505,2.819 +506,2.654 +507,2.748 +508,2.705 +509,2.798 +510,2.559 +511,2.694 +512,2.821 +513,2.706 +514,2.612 +515,2.928 +516,2.475 +517,2.830 +518,2.658 +519,2.784 +520,2.572 +521,2.935 +522,2.688 +523,2.600 +524,2.675 +525,2.828 +526,2.774 +527,2.848 +528,2.764 +529,2.379 +530,2.962 +531,2.989 +532,2.528 +533,2.639 +534,2.906 +535,2.543 +536,2.582 +537,2.949 +538,2.813 +539,2.504 +540,2.754 +541,2.638 +542,2.695 +543,2.662 +544,2.922 +545,2.616 +546,2.810 +547,2.675 +548,2.551 +549,2.894 +550,2.754 +551,2.632 +552,2.748 +553,2.827 +554,2.751 +555,2.759 +556,2.684 +557,2.766 +558,2.948 +559,2.461 +560,2.621 +561,2.772 +562,2.985 +563,2.582 +564,2.897 +565,2.574 +566,2.661 +567,2.742 +568,2.797 +569,2.632 +570,2.755 +571,2.878 +572,2.759 +573,3.812 +574,2.651 +575,2.862 +576,2.577 +577,2.634 +578,3.173 +579,2.418 +580,2.714 +581,2.746 +582,2.794 +583,2.774 +584,2.583 +585,3.659 +586,3.222 +587,2.489 +588,2.868 +589,2.558 +590,2.586 +591,2.599 +592,2.930 +593,2.401 +594,2.540 +595,2.889 +596,2.733 +597,2.664 +598,2.877 +599,2.588 +600,2.677 +601,2.897 +602,2.672 +603,2.780 +604,2.740 +605,2.780 +606,2.493 +607,2.794 +608,2.631 +609,2.768 +610,2.870 +611,2.626 +612,2.862 +613,2.602 +614,2.868 +615,2.627 +616,2.638 +617,2.702 +618,2.904 +619,2.918 +620,2.487 +621,3.225 +622,2.350 +623,2.820 +624,2.837 +625,2.408 +626,2.593 +627,3.526 +628,2.554 +629,2.746 +630,2.684 +631,2.729 +632,2.762 +633,2.768 +634,3.034 +635,2.514 +636,2.732 +637,2.739 +638,2.692 +639,2.832 +640,2.810 +641,2.484 +642,2.632 +643,3.284 +644,2.311 +645,3.079 +646,2.656 +647,2.683 +648,2.622 +649,2.668 +650,2.779 +651,2.796 +652,2.874 +653,2.427 +654,2.644 +655,2.872 +656,2.867 +657,2.404 +658,2.917 +659,2.773 +660,2.857 +661,2.789 +662,2.443 +663,2.651 +664,2.839 +665,2.814 +666,2.575 +667,2.796 +668,2.709 +669,2.873 +670,2.670 +671,2.936 +672,2.496 +673,2.608 +674,2.641 +675,2.793 +676,2.765 +677,2.736 +678,2.679 +679,2.987 +680,2.884 +681,2.469 +682,2.774 +683,2.948 +684,2.598 +685,3.478 +686,2.856 +687,4.623 +688,3.038 +689,2.507 +690,2.717 +691,2.682 +692,2.807 +693,2.778 +694,2.699 +695,2.802 +696,2.605 +697,2.650 +698,2.789 +699,2.791 +700,3.015 +701,2.539 +702,2.790 +703,2.698 +704,2.637 +705,2.657 +706,2.800 +707,2.720 +708,2.702 +709,2.845 +710,2.583 +711,2.780 +712,2.782 +713,2.593 +714,2.695 +715,2.747 +716,2.871 +717,2.671 +718,2.676 +719,2.991 +720,2.727 +721,2.576 +722,2.539 +723,2.770 +724,2.712 +725,2.818 +726,2.615 diff --git a/Docs/release-checks.md b/Docs/release-checks.md index ad2eb14..db26a74 100644 --- a/Docs/release-checks.md +++ b/Docs/release-checks.md @@ -127,6 +127,22 @@ Run on the packaged build (`GigLineQA`, Development, Vulkan SM5) with `-qaselfte Committed frame-time series: `Docs/perf/0.1.0-walk-frametimes.csv`. +### `0.1.5` re-measure with the façade kit in the map + +Same route, same preset, packaged Development build, **2560×1440**: + +| Metric | `0.1.0` | `0.1.5` (kit + textures) | +| ------- | ----------------: | -----------------------: | +| Average | 2.42 ms (414 fps) | **2.76 ms (363 fps)** | +| p50 | 2.38 ms | 2.73 ms | +| p95 | 2.71 ms | 3.17 ms | +| p99 | 3.01 ms | 3.64 ms | +| Max | 4.00 ms | 4.62 ms | + +The **625 kit actors and 8 generated textures cost about 12 %** of frame time and still leave **363 fps average, p99 3.64 ms** — roughly six times the 60 fps floor. Frame-time series: `Docs/perf/0.1.5-kit-walk-frametimes.csv`. + +**Measurement trap, again:** runs that reported ~105 fps were two game instances overlapping (a previous launch had not exited). Confirm a single instance (`pgrep -af Binaries/Linux/GigLine`) before trusting a figure — 363 fps reproduced twice on a clean run. + ## `0.1.0` — Fresh checkout build — record (2026-11-08) Followed the documented steps in [`Docs/build.md`](build.md) exactly, into a clean directory with no prior state: diff --git a/MEMORY.md b/MEMORY.md index 1f793d1..3a6caf0 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -474,3 +474,16 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. the stale literal cannot come back. Spawn-view score back to **0.72** (best yet). - Lesson to keep: **duplicated placement constants drift silently.** The metric that caught it was the composition correlation, not a visual review. + +## 2026-11-09 — 1440p re-measured with the kit: 363 fps, ~12% cost + +- 2560×1440, Epic preset, TSR, VSync off, no frame generation: **2.76 ms average + (363 fps)**, p50 2.73, p95 3.17, p99 3.64, max 4.62 — against `0.1.0`'s 2.42 ms + (414 fps). The 625 kit actors + 8 generated textures cost about **12 %** and leave + ~6× the 60 fps floor. +- **Variance trap confirmed a third time:** runs reporting ~105 fps (9.5 ms) were + two overlapping game instances — an earlier launch had not exited. `pgrep -af + Binaries/Linux/GigLine` should show one wrapper + one binary; 363 fps reproduced + twice on a clean run. Always count instances before believing a perf number. +- Series committed at `Docs/perf/0.1.5-kit-walk-frametimes.csv`; comparison table in + `Docs/release-checks.md`. diff --git a/TODO.md b/TODO.md index 77052c7..40c7e58 100644 --- a/TODO.md +++ b/TODO.md @@ -20,7 +20,7 @@ The `0.1.4` kit, materials and theatre trim are signed off; these are the geomet - [A] **Retail neighbours (6822 Budget, 6806–6820, 6800 Lee Drug) from their own references.** **Started 2026-11-09.** Fetched the one usable reference Commons holds for this run — `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA 4.0, 1920×953) — which shows 6800 **and** its neighbour at street level; the vision pre-check confirmed shopfronts and parapets are visible. The 18-frame theatre set cannot stand in for these façades, and Commons has nothing else for 6822 or 6806–6820, so per-building references for those two remain the blocker. **Kit layout fixed while here:** bay count was `round(width / 4.96)`, which gave 6922 thirteen bays across 65.8 m (a window every 4.96 m on a 37 m block) and left bare wall under the cornice; bays are now capped at 9 with the module widened, and the storey spread fills the available height (6922 9 × 11, 6806 7 × 3, 6800 4 × 3, theatre 8 × 6). - [x] **Hero façade: fluting and the motto band as geometry.** **Done 2026-11-09.** Fluting: 20 grooves per column (thin shadow-tone cylinders set into the shaft, 120 pieces) instead of boolean cuts — it breaks the flat highlight at a fraction of the poly cost. Motto band: a real recess now, with a moulded surround standing 6 cm proud and the panel set 12 cm behind it, replacing the 8 cm dark stripe. Hero is 271 objects / 5 492 tris (from 148 / 3 044) and still 14.50 m. **En route it exposed a placement bug:** `ue_hero_facade_import.py` carried its own stale hero position `(-12024.5, -1450)` from before the `0.1.0` frontage fix, so running the importer alone moved the hero 13 m east and onto the north side — invisible in a render, and it dropped the reference score from 0.69 to 0.56. Placement now lives in `Tools/ue_placement.py` with span validators, asserted after every import (`span ok: X -14644..-12028`), and the spawn-view score recovered to **0.72**. - [ ] **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. +- [x] **Re-measure 1440p performance** with 625 kit actors and 8 generated textures in the map. **Done 2026-11-09:** 2.76 ms average (363 fps), p99 3.64 ms at 2560×1440, Epic preset, TSR, VSync off, no frame generation — about **12 %** more frame time than `0.1.0` (2.42 ms / 414 fps) and still ~6× the 60 fps floor. Recorded in `Docs/release-checks.md`; series at `Docs/perf/0.1.5-kit-walk-frametimes.csv`. Note: runs showing ~105 fps were two overlapping game instances, not the build. - [ ] 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 600633cd5bb2037567bede9bb2110bc1b6196bec Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 22:48:00 +0100 Subject: [PATCH 4/5] fix(materials): stop orphaning references; add a scene audit gate The blockout cleaning pass found the map was rendering with 604 of 620 mesh actors unmaterialed - every kit piece and all theatre trim. Cause: ensure_material() deleted and recreated each /Game/Hollywood/Site/M_* asset on every run, so actors staged by other tools kept references to deleted assets and fell back to the default material. Nothing errored; the map looked merely slightly flat. - Materials are now updated IN PLACE (load, delete_all_material_expressions, rebuild the graph, recompile, save). Material.expressions is protected from Python, so the editing library's deleter is used. - 53 duplicated kit actor labels fixed (both shop piers of a bay shared one name). - New Tools/ue_audit_scene.py gates the scene: every mesh actor has a material, every verified frontage span is within +/-1 m (via ue_placement), no duplicate labels. - render_screenshots.sh now runs the chain in the required order (demo map -> kit -> theatre -> audit -> cameras -> capture) and exits non-zero on an audit failure, because the demo builder rebuilds materials and later staging must follow it. Scene audit: 0 failures. Packaged QA PASS. Spawn-view reference score 0.72. --- Docs/build.md | 5 + Game/GigLine/Content/Hollywood/DemoMap.umap | 4 +- .../Content/Hollywood/Site/M_Asphalt.uasset | 4 +- .../Content/Hollywood/Site/M_Concrete.uasset | 4 +- .../Hollywood/Site/M_FacadeBanner.uasset | 4 +- .../Hollywood/Site/M_FacadeGlass.uasset | 4 +- .../Hollywood/Site/M_FacadeStone.uasset | 4 +- .../Hollywood/Site/M_FacadeStoneShadow.uasset | 4 +- .../Content/Hollywood/Site/M_Ground.uasset | 4 +- .../Hollywood/Site/M_KerbConcrete.uasset | 4 +- .../Content/Hollywood/Site/M_RoadPaint.uasset | 4 +- .../Content/Hollywood/Site/M_Stucco.uasset | 4 +- .../Content/Hollywood/Site/Terrain_DEM.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_BC.uasset | 2 +- .../Hollywood/Textures/T_Asphalt_R.uasset | 2 +- .../Hollywood/Textures/T_Concrete_BC.uasset | 2 +- .../Hollywood/Textures/T_Concrete_R.uasset | 2 +- .../Textures/T_StoneAshlar_BC.uasset | 2 +- .../Hollywood/Textures/T_StoneAshlar_R.uasset | 2 +- .../Hollywood/Textures/T_Stucco_BC.uasset | 2 +- .../Hollywood/Textures/T_Stucco_R.uasset | 2 +- .../Theatre/SM_Trim_BladeSign.uasset | 2 +- .../Theatre/SM_Trim_BladeSignBracket.uasset | 2 +- .../Theatre/SM_Trim_BladeSignFrame.uasset | 2 +- .../Theatre/SM_Trim_MarqueeCanopy.uasset | 2 +- .../Theatre/SM_Trim_MarqueeFascia.uasset | 2 +- .../Theatre/SM_Trim_MarqueeSoffit.uasset | 2 +- Game/GigLine/Content/Test/ReviewMap.umap | 2 +- MEMORY.md | 21 ++++ TODO.md | 2 +- Tools/README.md | 1 + Tools/render_screenshots.sh | 33 +++-- Tools/ue_apply_facade_kit.py | 8 +- Tools/ue_audit_scene.py | 114 ++++++++++++++++++ Tools/ue_build_demo_map.py | 21 +++- 35 files changed, 226 insertions(+), 55 deletions(-) create mode 100644 Tools/ue_audit_scene.py diff --git a/Docs/build.md b/Docs/build.md index accf4a7..e6dd2da 100644 --- a/Docs/build.md +++ b/Docs/build.md @@ -86,8 +86,13 @@ The hero block and the demo level are built by a fixed sequence. Each step write | North import | `Tools/ue_import_north_buildings.py` | `/Game/Hollywood/North/*` | | Hero import | `Tools/ue_hero_facade_import.py` | `/Game/Hollywood/ElCapitan/SM_HeroFacade` | | Demo map | `Tools/ue_build_demo_map.py` | `/Game/Hollywood/DemoMap` | +| Façade kit assembly | `Tools/ue_apply_facade_kit.py` | kit actors in `DemoMap` | +| Theatre trim | `Tools/ue_stage_theatre_trim.py` | marquee + blade sign actors | +| Scene audit | `Tools/ue_audit_scene.py` | materials, placement spans, labels (gate) | | Review shots | `Tools/ue_build_review_map.py` + `Tools/render_screenshots.sh` | `Screenshots/` | +**Order matters.** `ue_build_demo_map.py` rebuilds the site materials, so any actor staged by another tool must be (re)staged _after_ it, or it keeps a reference to a material that no longer exists — that is how 604 actors once ended up untethered from their materials while the map only looked slightly flat. `Tools/render_screenshots.sh` runs the full chain in the right order and gates on `ue_audit_scene.py`. + Every Blender exporter calls [`Tools/fbx_export_preset.py`](../Tools/fbx_export_preset.py) (`ue_metric_v1` / `ue_character_v1`) instead of repeating the FBX argument block — one place for the scale conversion, the axis mapping and `mesh_smooth_type="FACE"` (without which every Unreal import warns about missing smoothing groups and round geometry arrives faceted). Two rules that the pipeline enforces rather than documents: diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 686adbe..9b1cfcc 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:1c4b312ef29760c4a167d97397c18d691d2060291b8f2bd1876a590b2b38a214 -size 919602 +oid sha256:979c81662cbb7cce37da5401a873c85b56a8da9f13a1e27790bd10f8098c54a4 +size 920287 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 83426cf..a8aa8b3 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:fb97fb7403d331fff1a29884eb1edef8c0bbbe50e6fb33cfbe9ff9c696d24e30 -size 7876 +oid sha256:42651b3f341042ff5b59565061b40e10f742cb040ad32159cc2497baaa53241f +size 9818 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index e587855..4990334 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:9c93e7576378561a245689f414d1046cff9e3de14329c8773f38e72abf736e76 -size 7881 +oid sha256:ac79d443e78c66dfdd0e6dcc826acb516157bb3b099643ca46a48a11ade12fd1 +size 9823 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index d10f0e7..96db4a4 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:e163fe98ecb0da87e920e5f685f4698392c7b00f5c0edb4d3497d61d2eeac98c -size 8667 +oid sha256:ed6a9b1306a3f54006dd09d33edea45635af4445f88f9b7b021a6be96226d0f8 +size 11054 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index 961b4b6..674550a 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:8f24d1f4d483cf6896103250cf5726f4ab258ecb14386d4e6fd5786ee00263b8 -size 8662 +oid sha256:d8c25c5cf5e02e8ae7eddb42d6ab3ab5eb1868021ea6373a4e01676223daf7fe +size 11049 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 2e46754..b52bd8f 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:f8d3097b908c53a6c63dd8c2320794212362d1f5e4b1b1ff65a3e0f50e70c895 -size 12131 +oid sha256:ee7e3321ede0f26ff779baa0dbfb1cba79682c00c3c604a57a6ff511df1e1a96 +size 16924 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index 9f382ec..beb0987 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:853bb97a0318147126cd22b3bf3b2e6781e9605aadd1ace75c9accbd4e04a3d6 -size 12161 +oid sha256:a63d50ddbe2b8674fa37ec42936e02ae20f604503966bb0eb9acd072c2061508 +size 16954 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index 6201c75..79b8458 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:8a8fbad4e48bbf53f1f3dbc3d19fdb497ade7a70e108e0f009829176dcb098b0 -size 7871 +oid sha256:f24e2319667b421615b371015edf38ae1645049a29a4ed07e79f8f780161b4ec +size 9813 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index dd99703..cc43899 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:9add1813503a7727aea65bb55249ea2eae84a47a349f2ee6f011eaddb6a4d592 -size 7901 +oid sha256:27dad21ae40278bc8f85b71f816b26f63cebf3032cf6f026dc827770ae70c112 +size 9843 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index c62ba86..a9892bc 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:5b08b4bc9d63293bd4a2b2db4e6a70c6854b309a579d7121fb065ea3d9834051 -size 7886 +oid sha256:dd1f661a78dc559fe32a7eba5b8de1c3c11eb02db915c6af75698233064f3bfd +size 9828 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 26e7b21..9620116 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:81dae9d6c50131436e01dacd27bacfbeb6186405d9d70c1b2858f51dff158df5 -size 8637 +oid sha256:79afcede88edfbdbca48cc80d414f90824c1b3c29f4e0d6fa20bcfd83956add4 +size 11024 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index 8dfa296..98a9441 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:a3ca6de1695fb13f5435977ffa8c8330c16075cc21a2a78fef17d1c1ee0ee7cd +oid sha256:60e5397f227314e5d59fd3783e9c31713db9ebcbdc29a34aa44cddb305d76ff9 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 c70868b..eba8aec 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:03f6ce0b402dd3a4cbfb93ca704ff81c1ac782d1a1f34929342c0cf499eecc19 +oid sha256:c5d7a3acd47b38355b6903b962d6b938ff1f5ddc602923f8e944c29a4674dee2 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 966fd6c..3bd6c08 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:ad6d2595d987f3e4045995aebf9e3f7845ad229beed37308e9d8f4c7c33e1106 +oid sha256:2454b2c9f7d9950d68af0402b8553acc3aa64aab1faee95fe71fe2b2efe4710e 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 8085bdb..d463d4e 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:918cc5acf1447085dab73d989c89ac1423fe1cba599ae566d5524cc26076d1a8 +oid sha256:782166930730273c3bc82ac7be18f084436d65da79bcff316a4f171e77e6ef93 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 cdaaede..27ff573 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:ae968668a57242ffa65b81297f4dcd8e3280111b9f3470e1ebd4fd92eaee4a88 +oid sha256:805d8f4ad82682c0d2409cf46e8a62a65438a38073b4a307ff498b3de912a696 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 c132640..ebb8dce 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:6549cec0e7c5cc24781c484d9901c2ceb3c7a9db25e74352ee1d0f8f619c90cf +oid sha256:2a0d33fee2e6475e7c0392c838a9d7b132c6c95db66f44f39247f1e6ea3ce456 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 d9e5552..c55cf87 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:ad209b9388a8aed7cea08b267eeab17abec06fbaa392b31378117f7d828fd9c5 +oid sha256:c3ba7d4166902628d5ca37b7d6f164d8e4bf8a58990777a49fe3871640645c6c 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 5e443c4..71ce6ce 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:516debf8a3076a5cf3fcc8e4ebc2e63d5bc6b290ec9865eb2f47118cea9c7e3f +oid sha256:f435587872a1f21006d6cbc466b01728110b8574ed8384287f414bf332b62cd6 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 8ab670b..7711f4e 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:69b431dd98f073b4987a3019cb8cc22ba23055663eb986a03be57104b2c2e1f2 +oid sha256:92a72806044679f1ddccf944775d8c2c1a131006030c17480788c70404734d13 size 17541 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset index 9805a83..d557461 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSign.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:360a19294a103b52b4c0b1e5af5befd9a55fc9471443a703f6e39948d9eed962 +oid sha256:5152da3143f8b6b00f3e595d5adf07f65b260a2f32032140450a34538767f14e size 72330 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset index 7b89f33..d18ceec 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignBracket.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:262d2eaac6f9e2ec2cd5217c68cd9ac4882e075b5d52e6d09dcc96e3886f56a8 +oid sha256:b3a7c47afac32e8601e9ac4649c2e16663dfda8d409241527bd1d2c7bbd7c32e size 72466 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset index 990106b..e0fdde9 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_BladeSignFrame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:516633d806cd4c97bc409b27ffea4533211a798e3811c45c2d6f09176ebd6fc5 +oid sha256:f135922b7e89057917176a516fd283f1b739e9c6960892b8838daabd67eecfe0 size 72406 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset index 6ce02c6..b09cfb1 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeCanopy.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e55efd7aee1bb44a6968bf9f82a431798631d20bfafa53d4198c9d436ca4848e +oid sha256:e3aaf3835733ebdaed780d33e8ee6c5ef2047e7f53de03c2984aafcde75830aa size 72370 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset index 5c3ac79..bbf4643 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeFascia.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9554845054a637f8101c6d4fe9f85bce8fd7fe5a532768f4b345151b628a5f6d +oid sha256:6e42c24ba5e098c001a4b7ef3a79ab7ca4077a6c29647a7f85037e8a7e97a07a size 72438 diff --git a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset index 33e2f67..f352bf4 100644 --- a/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset +++ b/Game/GigLine/Content/Hollywood/Theatre/SM_Trim_MarqueeSoffit.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f89567a63c72fde986c42ffcb0577b672ece498d81159250c985fd1915a7863 +oid sha256:ffdf61d494359fea1c003dda276fc8c25b943bca7b9dd209c41381705a1ca13f size 72413 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 8432086..22da558 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:24610a4a1230a866990aaff5491a77a56250f347773dd3130615b76b53713e4f +oid sha256:24297b54b7f130738e1b29acf2c806522a191d185fce1164e52da1d744d81bdd size 28811 diff --git a/MEMORY.md b/MEMORY.md index 3a6caf0..aa984c8 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -487,3 +487,24 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. twice on a clean run. Always count instances before believing a perf number. - Series committed at `Docs/perf/0.1.5-kit-walk-frametimes.csv`; comparison table in `Docs/release-checks.md`. + +## 2026-11-09 — 604 unmaterialed actors: material rebuild was delete+recreate + +- The "blockout cleaning pass" turned out to need **material and placement hygiene, + not more geometry**. `Tools/ue_audit_scene.py` (new) found **604 of 620 mesh actors + with no material**: kit pieces and theatre trim all untextured. +- **Cause:** `ensure_material` in `ue_build_demo_map.py` deleted and recreated each + `/Game/Hollywood/Site/M_*` asset every run ("so a look change cannot be masked by a + stale asset"). That orphane every reference: actors staged by other tools pointed at + deleted assets and fell back to the default material. The map looked only *slightly* + flat — no error, no warning. +- **Fix:** update the material **in place** — load it, `MaterialEditingLibrary. + delete_all_material_expressions(mat)`, rebuild the graph, recompile, save. Note + `Material.expressions` is protected from Python; use the library's deleter. +- **Ordering rule now documented and enforced:** the demo builder rebuilds materials, + so kit and theatre staging must run *after* it. `render_screenshots.sh` now runs + demo map → kit assembly → theatre trim → scene audit → shot cameras → capture, and + **fails on a non-zero audit count**. +- Second find: 53 duplicated kit labels (both shop piers of a bay shared one name). +- Scene audit after the fixes: **0 failures** (all materialed, all spans verified, no + duplicate labels). Packaged QA still PASS; spawn-view reference score 0.72. diff --git a/TODO.md b/TODO.md index 40c7e58..ef39e3e 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,7 @@ The `0.1.4` kit, materials and theatre trim are signed off; these are the geomet - [A] **Retail neighbours (6822 Budget, 6806–6820, 6800 Lee Drug) from their own references.** **Started 2026-11-09.** Fetched the one usable reference Commons holds for this run — `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA 4.0, 1920×953) — which shows 6800 **and** its neighbour at street level; the vision pre-check confirmed shopfronts and parapets are visible. The 18-frame theatre set cannot stand in for these façades, and Commons has nothing else for 6822 or 6806–6820, so per-building references for those two remain the blocker. **Kit layout fixed while here:** bay count was `round(width / 4.96)`, which gave 6922 thirteen bays across 65.8 m (a window every 4.96 m on a 37 m block) and left bare wall under the cornice; bays are now capped at 9 with the module widened, and the storey spread fills the available height (6922 9 × 11, 6806 7 × 3, 6800 4 × 3, theatre 8 × 6). - [x] **Hero façade: fluting and the motto band as geometry.** **Done 2026-11-09.** Fluting: 20 grooves per column (thin shadow-tone cylinders set into the shaft, 120 pieces) instead of boolean cuts — it breaks the flat highlight at a fraction of the poly cost. Motto band: a real recess now, with a moulded surround standing 6 cm proud and the panel set 12 cm behind it, replacing the 8 cm dark stripe. Hero is 271 objects / 5 492 tris (from 148 / 3 044) and still 14.50 m. **En route it exposed a placement bug:** `ue_hero_facade_import.py` carried its own stale hero position `(-12024.5, -1450)` from before the `0.1.0` frontage fix, so running the importer alone moved the hero 13 m east and onto the north side — invisible in a render, and it dropped the reference score from 0.69 to 0.56. Placement now lives in `Tools/ue_placement.py` with span validators, asserted after every import (`span ok: X -14644..-12028`), and the spawn-view score recovered to **0.72**. -- [ ] **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. +- [x] **Blosm blockout cleaning pass.** **Done 2026-11-09 — the cleaning it needed was material and placement hygiene, not more geometry.** Audit found **604 of 620 mesh actors with no material at all**: `ensure_material` deleted and recreated each site material on every run, orphaning every reference held by the 598 kit actors and the theatre trim, while the map looked merely slightly flat. Fixed by updating materials **in place** (`delete_all_material_expressions` then rebuild the graph) instead of delete+create. Also fixed 53 duplicated kit actor labels (both shop piers of a bay shared one name). New `Tools/ue_audit_scene.py` gates the chain on: every mesh actor has a material, every verified frontage span is within ±1 m, no duplicate labels — wired into `render_screenshots.sh`, which now runs the staging chain in the correct order and fails on an audit failure. Scene audit: **0 failures**. - [x] **Re-measure 1440p performance** with 625 kit actors and 8 generated textures in the map. **Done 2026-11-09:** 2.76 ms average (363 fps), p99 3.64 ms at 2560×1440, Epic preset, TSR, VSync off, no frame generation — about **12 %** more frame time than `0.1.0` (2.42 ms / 414 fps) and still ~6× the 60 fps floor. Recorded in `Docs/release-checks.md`; series at `Docs/perf/0.1.5-kit-walk-frametimes.csv`. Note: runs showing ~105 fps were two overlapping game instances, not the build. - [ ] Tag `v0.1.5` and write `CHANGELOG.md` entry; then remove this section. diff --git a/Tools/README.md b/Tools/README.md index 3d582e0..0a54062 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -22,6 +22,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | `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_audit_scene.py` | Gate on the DemoMap: no unmaterialed actor, every verified frontage span within tolerance (`ue_placement`), no duplicate actor labels. Writes `/tmp/gigline/scene-audit.txt`; `render_screenshots.sh --verify` fails on a non-zero failure count. Catches the class of defect that still renders plausibly. | | `ue_placement.py` | Single source of truth for where staged actors belong in the DemoMap (hero position, verified frontage spans) plus `span_error`/`describe_span_failure` validators. Exists because the hero position was written twice and drifted — the importer kept the pre-`0.1.0` value and moved the hero 13 m east onto the wrong side of the boulevard. No `unreal` import, so it is unit-tested. | | `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/render_screenshots.sh b/Tools/render_screenshots.sh index 6a9fd90..1d34493 100755 --- a/Tools/render_screenshots.sh +++ b/Tools/render_screenshots.sh @@ -20,18 +20,29 @@ PROJ="$REPO/Game/GigLine/GigLine.uproject" VERIFY="no" [ "${1:-}" = "--verify" ] && VERIFY="yes" -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 +run_cmdlet() { + local script="$1" label="$2" + echo "== $label ==" + ( cd "$ENGINE" && ./Engine/Binaries/Linux/UnrealEditor-Cmd "$PROJ" \ + -run=pythonscript -script="$REPO/Tools/$script" \ + -nullrhi -unattended -nopause -stdout ) +} + +# Order matters. The demo builder rebuilds the site materials, so any actor staged +# by another tool must be (re)staged after it or it keeps a reference to a material +# that no longer exists - that is how 604 actors ended up untextured once. +run_cmdlet ue_build_demo_map.py "1/5 rebuild the demo map (materials, site, frontage)" +run_cmdlet ue_apply_facade_kit.py "2/5 assemble the facade kit" +run_cmdlet ue_stage_theatre_trim.py "3/5 stage the theatre trim" +run_cmdlet ue_audit_scene.py "4/6 audit the scene (materials, placement, labels)" +run_cmdlet ue_build_review_map.py "5/6 stage the demo shot cameras" tail -3 /tmp/ue-review-map-result.txt 2>/dev/null || true -echo "== 2/3 capture the demo map ==" +echo "== 6/6 capture the demo map ==" ./Engine/Binaries/Linux/UnrealEditor-Cmd "$PROJ" /Game/Hollywood/DemoMap \ -game -windowed -ResX=1920 -ResY=1080 -nopause -nosplash -stdout -echo "== 3/3 collect the demo screenshots ==" +echo "== collect the demo screenshots ==" mkdir -p "$REPO/Screenshots/demo" rm -f "$REPO"/Screenshots/demo/review-*.png found=0 @@ -47,6 +58,14 @@ done [ "$found" = "1" ] || { echo "ERROR: no demo screenshots found"; exit 1; } ls -1 "$REPO/Screenshots/demo" +# The scene audit is a gate: a wrong-material or misplaced actor is exactly the +# kind of failure that still renders plausibly. +if grep -q "failure(s) ==" /tmp/gigline/scene-audit.txt 2>/dev/null; then + grep -E "FAIL|failure\(s\)" /tmp/gigline/scene-audit.txt | tail -5 + grep -q "== 0 failure(s) ==" /tmp/gigline/scene-audit.txt || { + echo "ERROR: scene audit failed — see /tmp/gigline/scene-audit.txt"; exit 1; } +fi + if [ "$VERIFY" = "yes" ]; then echo "== verify: does each shot show what it should? ==" cd "$REPO" diff --git a/Tools/ue_apply_facade_kit.py b/Tools/ue_apply_facade_kit.py index d3768a5..42c5091 100644 --- a/Tools/ue_apply_facade_kit.py +++ b/Tools/ue_apply_facade_kit.py @@ -156,11 +156,13 @@ def apply_to_actor(actor, kit, materials=None, prefix=None): 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)): + for piece, dx, side in (("Kit_ShopPier_L", -bay_w / 2 + 30.0, "L"), + ("Kit_ShopPier_R", bay_w / 2 - 30.0, "R")): if piece in kit: + # The side belongs in the label: both piers of a bay shared one + # label, which left 53 ambiguous pairs in the outliner. if _spawn(kit[piece], unreal.Vector(cx + dx, face_y, base_z), - f"{prefix}shop_pier_{bay}", kit_material(piece)): + f"{prefix}shop_pier_{side}_{bay}", kit_material(piece)): placed += 1 if "Kit_ShopGlass" in kit: if _spawn(kit["Kit_ShopGlass"], unreal.Vector(cx, face_y, base_z), diff --git a/Tools/ue_audit_scene.py b/Tools/ue_audit_scene.py new file mode 100644 index 0000000..cf05e54 --- /dev/null +++ b/Tools/ue_audit_scene.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Audit the DemoMap for staged actors that would render wrong. + +Checks that are cheap and catch the failures this project actually had: + +1. **No unmaterialed actors.** 604 actors once rendered with the default material + because the demo builder deleted and recreated the site materials, orphaning + every reference; the map looked merely "a bit flat". +2. **Placement spans.** Each verified frontage building is probed against + `Tools/ue_placement.py`; a building 13 m off its footprint still renders fine, + which is exactly why it needs a number, not an eye. +3. **No duplicate actor labels.** Two buildings' kit pieces sharing a label made + the outliner ambiguous and hid one of them. + + UnrealEditor-Cmd -run=pythonscript \ + -script=Tools/ue_audit_scene.py -nullrhi -unattended -nopause -stdout + +Result file: `/tmp/gigline/scene-audit.txt`. Exit code from the commandlet is +always 0, so **read the file** — `visual_loop.py --verify` does not depend on it. +""" + +from __future__ import annotations + +import collections +import os +import sys + +import unreal + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import ue_placement # noqa: E402 + +MAP = "/Game/Hollywood/DemoMap" +RESULT = "/tmp/gigline/scene-audit.txt" + +lines: list[str] = [] +failures = 0 + + +def log(message: str) -> None: + lines.append(message) + print(message) + + +def fail(message: str) -> None: + global failures + failures += 1 + log("FAIL " + message) + + +def main() -> None: + if not unreal.EditorLevelLibrary.load_level(MAP): + log(f"could not load {MAP}") + return + + actors = unreal.EditorLevelLibrary.get_all_level_actors() + + # 1. materials + unmaterialed = [] + for actor in actors: + comp = actor.get_component_by_class(unreal.StaticMeshComponent) + if not comp or not comp.static_mesh: + continue + if comp.get_material(0) is None: + unmaterialed.append(actor.get_actor_label()) + log(f"mesh actors: {sum(1 for a in actors if a.get_component_by_class(unreal.StaticMeshComponent))}") + if unmaterialed: + fail(f"{len(unmaterialed)} actor(s) with no material, e.g. {unmaterialed[:6]}") + else: + log("OK every staged mesh has a material") + + # 2. placement spans + for actor in actors: + label = actor.get_actor_label() + key = None + if label.startswith("Frontage_SM_"): + name = label[len("Frontage_SM_"):] + key = {"Hollywood_6922": "6922", "Hollywood_6902": "6902", + "ElCapitanTheatre": "6838", "Budget_6822": "6822", + "Hollywood_6806": "6806", "LeeDrug_6800": "6800"}.get(name) + elif label == ue_placement.HERO_ACTOR: + key = "hero" + if not key: + continue + origin, extent = actor.get_actor_bounds(False) + lo, hi = origin.x - extent.x, origin.x + extent.x + problem = ue_placement.describe_span_failure(key, lo, hi) + if problem: + fail(problem) + else: + log(f"OK {key}: X {lo:.0f}..{hi:.0f}") + + # 3. duplicate labels + counts = collections.Counter(a.get_actor_label() for a in actors) + duplicates = {k: v for k, v in counts.items() if v > 1} + if duplicates: + sample = list(duplicates.items())[:6] + fail(f"{len(duplicates)} duplicated actor label(s), e.g. {sample}") + else: + log("OK no duplicate actor labels") + + log(f"== {failures} failure(s) ==") + + +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") diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index f125935..4a57c66 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -118,14 +118,23 @@ def ensure_material(mat_name, spec, textures=None): does not read as one flat specular value. """ path = f"/Game/Hollywood/Site/{mat_name}" - if unreal.EditorAssetLibrary.does_asset_exist(path): - # Rebuilt every run so a look change cannot be masked by a stale asset. - unreal.EditorAssetLibrary.delete_asset(path) - - mat = unreal.AssetToolsHelpers.get_asset_tools().create_asset( - mat_name, "/Game/Hollywood/Site", unreal.Material, unreal.MaterialFactoryNew()) lib = unreal.MaterialEditingLibrary + # Update the existing material IN PLACE rather than deleting and recreating it. + # Deleting the asset to force a look rebuild silently orphaned every reference: + # after one demo-builder run, all 598 kit actors and the theatre trim pointed at + # deleted materials and rendered with the default one (604 actors with no + # material), while the map itself looked merely "a bit flat". + mat = unreal.EditorAssetLibrary.load_asset(path) + if mat is None: + mat = unreal.AssetToolsHelpers.get_asset_tools().create_asset( + mat_name, "/Game/Hollywood/Site", unreal.Material, unreal.MaterialFactoryNew()) + else: + # Clear the old graph so a removed node cannot survive a look change. + # `Material.expressions` is protected from Python, so use the editing + # library's own deleter rather than reading the array. + lib.delete_all_material_expressions(mat) + def node(cls): return lib.create_material_expression(mat, cls) -- 2.47.3 From 73635df1a11eac453112dce7ff705703d403cab8 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 8 Nov 2026 22:54:41 +0100 Subject: [PATCH 5/5] =?UTF-8?q?release:=20v0.1.5=20=E2=80=94=20changelog?= =?UTF-8?q?=20entry,=20TODO=20pruning,=20roadmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGELOG: full 0.1.5 entry (scene audit, ue_placement, hero fluting, recessed motto band, kit layout fix, the 604-unmaterialed-actors fix, the 13 m hero placement drift, 363 fps re-measure, limitations). - TODO: 0.1.5 section removed; blocked retail frontage carried into a new 0.1.6 patch with the reason recorded, rather than letting the release wait on missing reference photography. - ROADMAP status updated. --- CHANGELOG.md | 39 ++++++++++++++++++++++++++++++++++++++- MEMORY.md | 15 +++++++++++++++ ROADMAP.md | 2 +- TODO.md | 14 +++++--------- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e188b4e..6753db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,42 @@ Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [ Nothing yet. +## [0.1.5] — 2026-11-09 + +Façade finishing. The kit gets the hero detail it was missing, and a class of silent defect is found and gated: the map had been rendering with **604 of 620 mesh actors unmaterialed** without a single error. + +### Added + +- **Scene audit** (`Tools/ue_audit_scene.py`): fails when any staged mesh has no material, when a frontage building is more than ±1 m off its verified footprint, or when two actors share a label. Wired into `Tools/render_screenshots.sh`, which now runs the whole staging chain in the correct order and stops on an audit failure. +- **`Tools/ue_placement.py`**: one source of truth for staged positions and the verified frontage spans, with `span_error` / `describe_span_failure` validators. +- **Hero fluting**: 20 grooves per column as shallow shadow-tone cylinders (120 pieces) rather than boolean cuts. +- **Presence of references for the retail run**: `Art/reference/retail-neighbours/` (6800 Lee Drug and its neighbour). Local only, not committed. + +### Changed + +- **Motto band is a real recess** now — moulded surround 6 cm proud, panel 12 cm behind — instead of an 8 cm dark stripe. Hero: 148 parts / 3 044 tris → 271 / 5 492, still 14.50 m. +- **Kit layout for wide and tall buildings**: bays are capped at 9 with the module widened (6922 was 13 bays across 65.8 m — a window every 4.96 m on a 37 m block), and the storey spread is derived from the available height so the top band always lands inside the volume. + +### Fixed + +- **604 of 620 mesh actors rendered untextured.** `ensure_material` deleted and recreated each site material on every run, orphaning every reference held by the kit pieces and the theatre trim; the map looked only slightly flat. Materials are now updated in place. +- **The hero could be teleported 13 m east onto the wrong side of the boulevard.** `ue_hero_facade_import.py` carried its own copy of the pre-`0.1.0` hero position; running the importer alone moved the building, invisible in a render and visible only as a reference-score drop. Placement now has one definition and the import asserts the staged span (`span ok: X -14644..-12028`). +- **53 duplicated kit actor labels** (both shop piers of a bay shared one name). + +### Verified + +- Scene audit: **0 failures** — every mesh materialed, all 7 verified spans within tolerance, no duplicate labels. +- Packaged `-qaselftest`: walk, reset, pause/unpause, seam transect 1456 samples / 0 holes, wall probe 0/35, façade probe 0/9 — all PASS. +- **1440p re-measured with the kit**: 2.76 ms average (**363 fps**), p99 3.64 ms, against `0.1.0`'s 2.42 ms (414 fps) — the 625 kit actors and 8 textures cost about 12 % and leave roughly six times the 60 fps floor. Series at `Docs/perf/0.1.5-kit-walk-frametimes.csv`. +- 78 tool tests pass. + +### Known limitations + +- **Retail neighbours 6822 and 6806–6820 are not modelled**: Commons holds no usable reference for either, and substituting the El Capitan Theatre's photographs would misattribute their façades. Tracked in `TODO.md`. +- Cornice dentil spacing, abacus and plinth dimensions remain recorded assumptions. +- Textures are generated, not authored — no decals, signage text or weathering maps. +- The vision pre-check stays advisory; it has rejected shots the owner approved. + ## [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. @@ -172,7 +208,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.4...main +[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.5...main +[0.1.5]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.5 [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 diff --git a/MEMORY.md b/MEMORY.md index aa984c8..de67cf7 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -508,3 +508,18 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - Second find: 53 duplicated kit labels (both shop piers of a bay shared one name). - Scene audit after the fixes: **0 failures** (all materialed, all spans verified, no duplicate labels). Packaged QA still PASS; spawn-view reference score 0.72. + +## 2026-11-09 — v0.1.5 released + +- All actionable `0.1.5` work is done: hero fluting + recessed motto band, kit layout + for wide/tall buildings, placement single-source-of-truth, 1440p re-measure + (363 fps), and the material/placement hygiene that the "blockout cleaning" task + actually needed (604 unmaterialed actors, 53 duplicate labels) with a new scene + audit gate. +- Only item carried forward is the **retail frontage (6822 / 6806–6820 / 6800)**, + blocked because Commons holds no usable reference for two of the three buildings + and substituting the theatre's photographs would misattribute the façades. It now + lives in a `0.1.6` Retail frontage patch. +- Precedent applied again: tag when the stated scope is done, carry blocked scope + into the next patch rather than letting a release wait indefinitely on missing + reference photography. diff --git a/ROADMAP.md b/ROADMAP.md index de7162b..00c8232 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,7 +8,7 @@ Development roadmap for this project: milestone goals, scope, decisions, and rel ## Status at a glance — 2026-11-08 15:30 -Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. **`v0.1.0` released 2026-11-08**: the walkable demo, hero façade, verified block frontage, first-person controls, ODbL attribution, and every release check pass (traversal, placement, collision, 60 fps at 1440p, fresh-checkout build, packaged build with controls). Next: the `0.1.1`–`0.1.4` patch series (collision detail, scale/façade corrections, packaging fixes, real building façades), then `0.2.0` (player body, third-person camera, animation). +Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. **`v0.1.0` released 2026-11-08**: the walkable demo, hero façade, verified block frontage, first-person controls, ODbL attribution, and every release check pass (traversal, placement, collision, 60 fps at 1440p, fresh-checkout build, packaged build with controls). Next: the `0.1.1`–`0.1.4` patch series (collision detail, scale/façade corrections, packaging fixes, real building façades), **Next:** `0.1.6` retail frontage (6822, 6806–6820, 6800 — blocked on reference photography for two of the three), then `0.2.0` (player body, third-person camera, animation). | Area | State | | --- | --- | diff --git a/TODO.md b/TODO.md index ef39e3e..d55e52d 100644 --- a/TODO.md +++ b/TODO.md @@ -9,19 +9,15 @@ 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.5 — Façade finishing (patch) +## 0.1.6 — Retail frontage (patch) -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. - -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. +Suggested blender skills: **references** (reference-image-match), **archviz**, **historical-worlds** for the 1920s–1990s retail mix on this run. ### Patch tasks -- [A] **Retail neighbours (6822 Budget, 6806–6820, 6800 Lee Drug) from their own references.** **Started 2026-11-09.** Fetched the one usable reference Commons holds for this run — `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA 4.0, 1920×953) — which shows 6800 **and** its neighbour at street level; the vision pre-check confirmed shopfronts and parapets are visible. The 18-frame theatre set cannot stand in for these façades, and Commons has nothing else for 6822 or 6806–6820, so per-building references for those two remain the blocker. **Kit layout fixed while here:** bay count was `round(width / 4.96)`, which gave 6922 thirteen bays across 65.8 m (a window every 4.96 m on a 37 m block) and left bare wall under the cornice; bays are now capped at 9 with the module widened, and the storey spread fills the available height (6922 9 × 11, 6806 7 × 3, 6800 4 × 3, theatre 8 × 6). -- [x] **Hero façade: fluting and the motto band as geometry.** **Done 2026-11-09.** Fluting: 20 grooves per column (thin shadow-tone cylinders set into the shaft, 120 pieces) instead of boolean cuts — it breaks the flat highlight at a fraction of the poly cost. Motto band: a real recess now, with a moulded surround standing 6 cm proud and the panel set 12 cm behind it, replacing the 8 cm dark stripe. Hero is 271 objects / 5 492 tris (from 148 / 3 044) and still 14.50 m. **En route it exposed a placement bug:** `ue_hero_facade_import.py` carried its own stale hero position `(-12024.5, -1450)` from before the `0.1.0` frontage fix, so running the importer alone moved the hero 13 m east and onto the north side — invisible in a render, and it dropped the reference score from 0.69 to 0.56. Placement now lives in `Tools/ue_placement.py` with span validators, asserted after every import (`span ok: X -14644..-12028`), and the spawn-view score recovered to **0.72**. -- [x] **Blosm blockout cleaning pass.** **Done 2026-11-09 — the cleaning it needed was material and placement hygiene, not more geometry.** Audit found **604 of 620 mesh actors with no material at all**: `ensure_material` deleted and recreated each site material on every run, orphaning every reference held by the 598 kit actors and the theatre trim, while the map looked merely slightly flat. Fixed by updating materials **in place** (`delete_all_material_expressions` then rebuild the graph) instead of delete+create. Also fixed 53 duplicated kit actor labels (both shop piers of a bay shared one name). New `Tools/ue_audit_scene.py` gates the chain on: every mesh actor has a material, every verified frontage span is within ±1 m, no duplicate labels — wired into `render_screenshots.sh`, which now runs the staging chain in the correct order and fails on an audit failure. Scene audit: **0 failures**. -- [x] **Re-measure 1440p performance** with 625 kit actors and 8 generated textures in the map. **Done 2026-11-09:** 2.76 ms average (363 fps), p99 3.64 ms at 2560×1440, Epic preset, TSR, VSync off, no frame generation — about **12 %** more frame time than `0.1.0` (2.42 ms / 414 fps) and still ~6× the 60 fps floor. Recorded in `Docs/release-checks.md`; series at `Docs/perf/0.1.5-kit-walk-frametimes.csv`. Note: runs showing ~105 fps were two overlapping game instances, not the build. -- [ ] Tag `v0.1.5` and write `CHANGELOG.md` entry; then remove this section. +- [ ] **Model 6822 Budget, 6806–6820 and 6800 Lee Drug from their own references.** `Art/reference/retail-neighbours/` holds 6800 and its neighbour (Gb321, CC BY-SA 4.0); Commons has nothing usable for 6822 or 6806–6820, so those two need photography before they can be modelled honestly. Do not substitute the El Capitan Theatre's 18-frame set — it would misattribute the façades. +- [ ] Review every façade change with `Tools/render_screenshots.sh --verify` before tagging. +- [ ] Tag `v0.1.6` 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