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)