diff --git a/Art/blender/retail/ref-analysis.md b/Art/blender/retail/ref-analysis.md new file mode 100644 index 0000000..c55f811 --- /dev/null +++ b/Art/blender/retail/ref-analysis.md @@ -0,0 +1,64 @@ +# Reference analysis — retail frontage, 0.1.6 + +Phase 0 of the `references` pack (reference-image-match). Written before any geometry, because the pass that produced the hero façade skipped it and cost two rebuilds. + +## Source + +| | | +| --- | --- | +| Image | `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (1920×953) | +| Author / licence | Gb321, own work, **CC BY-SA 4.0** — attribution required | +| Captured | 2024-07-19 11:11 local | +| Description | "Lee Drug and 6806 Hollywood Buildings in Hollywood" | +| Coverage | Both target buildings in one frame, mid-morning sun from the south-east | + +**One photograph is the whole evidence base for 6800 and 6806–6820.** It is a street-level oblique, not an elevation: verticals converge, the ground floor of 6806–6820 is partly behind a parked car, and the Lee Drug shopfront is behind a bus. Everything below that the photo does not resolve is marked _assumed_ and is carried into the part report. + +**6822 Budget has no photograph.** It is a 6.0 m × 5.5 m single-storey sliver west of 6806–6820, and the TODO item already records that Commons has nothing usable. It stays a blockout extrusion and the item is deferred rather than guessed. + +## Measured (not from the photo) + +From `Geo/raw/osm-api-map-2026-11-06.osm` and the blockout, in the shared frame (metres, X east, Y north, Z from the project datum): + +| Building | OSM way | X | Front face Y | Height | Depth | +| --- | --- | --- | --- | --- | --- | +| 6800 Lee Drug | 424024149 | −33.20 … −14.37 | −14.63 | 12.10 m | 33.5 m | +| 6806–6820 | 424024150 | −66.08 … −33.07 | −12.52 | 12.10 m | 30.8 m | +| 6822 Budget | 424024144 | −78.54 … −72.52 | −12.92 | 5.50 m | 15.4 m | + +Both target buildings are 12.10 m tall, which makes the _relative_ proportions in the photo directly usable: the parapet line, the cornice and the shopfront line can be read as fractions of the frame height. + +## 6800 Lee Drug — reading + +| Category | Observation | +| --- | --- | +| Subject | Corner retail block at Hollywood × Highland; the street face and the corner return are both visible | +| Silhouette | Long low box, broken by a **rounded corner tower** that rises above the frontage parapet; the parapet is **stepped**, not flat | +| Storeys | Two zones, not storeys: a tall shopfront zone and a single tall display wall above it | +| Proportion (vertical) | Shopfront ≈ 0 → 0.36 H; bronze cornice ≈ 0.36 → 0.43 H; mural + lettering wall ≈ 0.43 → 0.82 H; stepped parapet ≈ 0.82 → 1.0 H (_assumed split; read off the frame, ±0.03 H_) | +| Proportion (horizontal) | Corner tower occupies ≈ 4 m of the east end; eight mural panels at roughly 2.2 m centres along the frontage | +| Colour | Cream stucco wall `#EDE6D8`, bronze/brown cornice `#6B4A2F`, near-black mural plates `#1C1A18`, dark glazing `#20262A` (_sampled from the frame_) | +| Unique features | Stepped Deco parapet with 4 horizontal reveal lines; projecting bronze cornice; row of black celebrity-portrait panels; large dark "SOUVENIRS OF …" lettering on the upper wall; shopfront glazing with slim mullions | +| Mood | 1930s Deco retail, freshly painted, bright midday | + +## 6806–6820 — reading + +| Category | Observation | +| --- | --- | +| Silhouette | Flat-fronted two-storey block; parapet steps up in fins at the west end | +| Proportion (vertical) | Shopfront ≈ 0 → 0.32 H; burgundy fascia band ≈ 0.32 → 0.38 H; window storey ≈ 0.38 → 0.62 H; blank wall + parapet ≈ 0.62 → 1.0 H (_assumed_) | +| Horizontal | Brown pilaster strips divide the frontage into roughly 3 m bays; seven upper windows across 33 m | +| Colour | Same cream stucco; brown pilasters `#6B4A2F`; **burgundy-green** fascia `#5B3A3A`; white window frames | +| Unique features | Banded parapet with 3 reveal lines; white-framed upper windows with security bars; shopfront glazing behind a burgundy fascia | + +## Camera match (for the review shot) + +The photo is a street-level oblique from the **north** side looking south-east, subject filling ~80 % of the frame width. The demo review shot must reproduce that: camera at 1.7 m on the north sidewalk, yaw toward the south frontage, 35–42° lens, both buildings in frame. A wide block view will not compare usefully against this crop. + +## Assumptions to carry into the model report + +1. Vertical proportions, as above — the photo's convergence makes a pixel-perfect read impossible. +2. Mural panel size and spacing; the lettering is modelled as a recessed plate, not as text. +3. Shopfront bay spacing on both buildings; the bus hides most of Lee Drug's ground floor. +4. Parapet reveal line count (4 on Lee Drug, 3 on 6806–6820). +5. Corner tower radius; only the outer curve is visible. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6753db6..f60953e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,46 @@ Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [ Nothing yet. +## [0.1.6] — 2026-11-09 + +Retail frontage. The two retail buildings beside the hero are modelled from their own photograph instead of the generic kit, and the photograph is treated as evidence: an analysis is written before any geometry, and every dimension the image does not resolve is carried into the part report as an assumption. + +### Added + +- **`Tools/retail_frontage_build.py`**: 6800 Lee Drug and 6806–6820 built from the one usable reference (`Art/reference/retail-neighbours/`, Gb321, CC BY-SA 4.0). Lee Drug gets a bronze cornice, storefront glazing with mullions, framed portrait panels, a painted lettering band, two-tier parapet with reveals and a fluted corner tower; 6806–6820 gets a burgundy fascia, brown pilasters, white-framed windows with security bars and its own banded parapet. Built in world coordinates from the blockout bounds (Lee Drug X −33.20…−14.37, face Y −14.63, H 12.10 m; 6806–6820 X −66.08…−33.07), joined to one mesh per building with a JSON part report. +- **`Tools/ue_import_retail_detail.py`**: imports the two meshes with complex-as-simple collision — trim decorates a solid the player already collides with and must not add snag points. +- **`Art/blender/retail/ref-analysis.md`**: the reference analysis the `references` skill pack requires, written before modelling. +- **`Tools/_common.py`** and `Tools/tests/test_retail_frontage.py`: the bay-layout arithmetic, extracted so it is testable without Blender (4 tests). +- **Review shot 5**, `demo/review-demo-retail-frontage`, embedded in `Screenshots/README.md`. + +### Changed + +- **The generic façade kit skips the two modelled buildings** (`MODELLED` list in `ue_apply_facade_kit.py`). It applies to every `Frontage_*`/`North_*` actor, so it was pasting a second, contradicting façade language over the new trim. +- **Retail trim stages as `RetailTrim_*`** and is spawned through the demo builder, so it is regenerable rather than hand-placed. +- Trim stucco, white and glass slots are mapped onto the demo's look-dev materials so the walls sit in the same noise and texture family as the rest of the block; the tones sampled from the photograph (bronze, burgundy, mural, lettering) stay as authored. + +### Fixed + +- **`Tools/render_screenshots.sh` never captured anything.** The capture step called `./Engine/...` while every commandlet above it went through `run_cmdlet`'s `cd "$ENGINE"`. Under `set -e` the script died at exit 127 _after_ rebuilding the maps, so the PNGs silently stayed a day old and `--verify` never ran. +- **Duplicate `RetailTrim_*` actors on every rebuild**: the demo builder's clear list did not include the new label, so each run stacked another copy. Caught by the scene audit, not by eye. +- **Mural plates were placed 0.20 m inside the solid blockout volume** and never rendered; the inset is now faked with a proud frame and a near-flush plate. +- **Trim detail sat 0.05 m off the wall**, under a pixel of depth at 40 m, so the wall won the depth test. Trim planes now stand 0.10–0.20 m proud. +- **6806–6820 parapet started at 0.62 H**, leaving a 4.6 m blank band; the window storey and parapet moved up to follow the photograph. +- **Mural panels, parapet and lettering were sized from the photograph's proportions, not the inherited defaults**: the first pass left a parapet that read as one blank white band. + +### Verified + +- `render_screenshots.sh --verify`: **5/5 shots OK**; scene audit **0 failures** (every mesh materialed, all spans in tolerance, no duplicate labels). +- Owner approved `demo-retail-frontage` (second pass) 2026-11-09; all five demo shots now carry approval. +- 82 tool tests pass. + +### Known limitations + +- **Wall lettering and the painted band do not read at 40 m.** A plate at 0.10 m still vanishes; this is a decal/texture problem, not geometry. Moved to `0.9.0 — Realistic buildings`. +- **The parapet has no moulded profile** and the corner tower is a fluted cylinder with two cap rings — both need the façade system in `0.9.0`. +- **6822 Budget is untouched**: no photograph of it exists. Deferred and blocked in `0.9.0`. +- The only reference for these two buildings is a single street-level oblique with a bus across Lee Drug's shopfront and a car across 6806's; several vertical proportions are assumptions recorded in the part report. + ## [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. diff --git a/Docs/Architecture/05_building_block_view.md b/Docs/Architecture/05_building_block_view.md index ac1ca0e..45bc8fb 100644 --- a/Docs/Architecture/05_building_block_view.md +++ b/Docs/Architecture/05_building_block_view.md @@ -49,6 +49,7 @@ Static meshes and materials in `/Game/Hollywood/Site/`: | `SM_HeroFacade` | El Capitan Entertainment Centre detailed façade | | `SM_ElCapitanTheatre`, `SM_Budget_6822`, `SM_Hollywood_6806`, `SM_Hollywood_6902`, `SM_Hollywood_6922`, `SM_LeeDrug_6800` | South block frontage (6 buildings) | | `SM_Madame_Tussauds`, `SM_TCL_Chinese_Theatre` | North-side buildings (within 50 m) | +| `SM_Retail_LeeDrug`, `SM_Retail_6806` | Retail trim over 6800 and 6806–6820, modelled from their own photograph (`Tools/retail_frontage_build.py`, staged as `RetailTrim_*`). `6822 Budget` stays a bare volume: no photograph exists. | ### Art Pipeline @@ -59,11 +60,14 @@ Python scripts in `Tools/` drive the content pipeline: | `export_*`, `hero_facade_build` | Blender headless export (FBX) | | `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement | | `character_anim_build`, `ue_character_anim_import` | Character animation clips (`GigLine_Idle`, `GigLine_Walk`) and their import | +| `export_*`, `hero_facade_build`, `retail_frontage_build`, `theatre_trim_build`, `facade_kit_build` | Blender headless export (FBX); trim builders work in world coordinates | +| `ue_import_*`, `ue_hero_facade_import`, `ue_import_retail_detail` | Unreal headless import + placement | | `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) | | `ue_create_input_assets` | Enhanced Input actions + mapping context | | `render_screenshots.sh` | Screenshot capture pipeline | | `judge_screenshot.py` | Vision-model pre-check (optional) | | `input_emulation.py`, `input_test.py` | OS-level virtual keyboard/mouse injection for input verification | +| `Tools/_common.py` | Pure helper shared by Blender builders, importable without Blender (bay layout) | | `Tools/tests/` | Stdlib `unittest` tests for the pure parts of the tools (`requirements-tools.txt`) | | `Geo/tools/mapbox_buildings.py` | Second height source: Mapbox `mapbox-streets-v8` building data vs our OSM heights | diff --git a/Docs/Architecture/09_architecture_decisions.md b/Docs/Architecture/09_architecture_decisions.md index 343f648..704160e 100644 --- a/Docs/Architecture/09_architecture_decisions.md +++ b/Docs/Architecture/09_architecture_decisions.md @@ -56,6 +56,14 @@ - **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI. +## ADR-8: Realistic building façades are a minor milestone, not a patch + +- **Context**: `0.1.4` carried the first façade pass as a _patch_, while the release policy says patches contain fixes and refinements and never new scope. The `0.1.6` retail pass then showed what that costs: a façade built in a patch has no release gate of its own, so a first pass that does not yet read as the building (`Screenshots/README.md`, 0.1.6 gaps) can sit inside a "done" patch series. Realism is also the project's leading quality goal, which a patch cannot carry. + +- **Decision**: Building realism gets its own minor milestone, `0.9.0 — Realistic buildings`, placed before the feature freeze. Its scope is the façade system (orders, profile library, storefront types, signage as decals), per-building photo-matched models, a measured material library with a texel-density rule, and LODs plus collision proxies. Patches keep fixes and refinements only. Renumbered by this decision: the former `0.9.0` freeze/beta/audit milestone becomes `0.10.0`, its patches `0.10.1`–`0.10.3`, and `1.0.0` moves to weeks 39–44. + +- **Consequences**: Façade work now has a release gate — every building measured against its own photograph, every building with a collision proxy and a LOD, no placeholder materials — so "modelled" means verified, not merely present. The price is that realism work stops being sliced into patches, and the schedule shifts by one minor cycle. Blockouts without a photograph stay blockouts and are listed as such, rather than being dressed up to fill the milestone. + ## ADR-9: Third-person body is an attachment of the first-person pawn, not a second pawn - **Context**: `0.2.0` needs a visible body and a third-person camera, while `0.1.0` shipped a first-person pawn with no body and the owner's decision was first person for that release. Two separate pawn classes would duplicate movement, input and reset logic, which is the code most likely to drift. diff --git a/Docs/Architecture/10_quality_requirements.md b/Docs/Architecture/10_quality_requirements.md index 6a5a48c..718e4bf 100644 --- a/Docs/Architecture/10_quality_requirements.md +++ b/Docs/Architecture/10_quality_requirements.md @@ -33,3 +33,13 @@ Ordered by priority: realism first, performance last. The order decides design t | QS-9 | Game runs on dev workstation at 1440p | Frame rate holds the floor | ≥ 60 FPS, 99th percentile ≥ 55 FPS | > QS-9 is last on purpose. Performance is a floor that every release must meet, not the goal that shapes the content: realism decides what gets built, the frame budget decides how much of it. + +### Building realism gate (0.9.0) + +| ID | Check | Metric | +| --- | --- | --- | +| BR-1 | A building with a reference photograph has a review shot compared against it | Silhouette and proportion deltas recorded per building in `Screenshots/README.md`; owner approval per shot | +| BR-2 | Every building is either photo-matched or explicitly recorded as an unfounded blockout | List in `TODO.md`; no building in an in-between state | +| BR-3 | Every building has a collision proxy and at least one LOD | Count of buildings missing either = 0 | +| BR-4 | No surface carries a placeholder material | Material audit in `Tools/ue_audit_scene.py` reports 0 placeholder slots | +| BR-5 | The realism pass does not break the floor | QS-9 still holds at 1440p, with the measured cost of the pass recorded | diff --git a/Docs/Architecture/architecture.md b/Docs/Architecture/architecture.md index 3a5062a..1b2b491 100644 --- a/Docs/Architecture/architecture.md +++ b/Docs/Architecture/architecture.md @@ -193,6 +193,7 @@ Static meshes and materials in `/Game/Hollywood/Site/`: | `SM_HeroFacade` | El Capitan Entertainment Centre detailed façade | | `SM_ElCapitanTheatre`, `SM_Budget_6822`, `SM_Hollywood_6806`, `SM_Hollywood_6902`, `SM_Hollywood_6922`, `SM_LeeDrug_6800` | South block frontage (6 buildings) | | `SM_Madame_Tussauds`, `SM_TCL_Chinese_Theatre` | North-side buildings (within 50 m) | +| `SM_Retail_LeeDrug`, `SM_Retail_6806` | Retail trim over 6800 and 6806–6820, modelled from their own photograph (`Tools/retail_frontage_build.py`, staged as `RetailTrim_*`). `6822 Budget` stays a bare volume: no photograph exists. | ### Art Pipeline @@ -203,11 +204,14 @@ Python scripts in `Tools/` drive the content pipeline: | `export_*`, `hero_facade_build` | Blender headless export (FBX) | | `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement | | `character_anim_build`, `ue_character_anim_import` | Character animation clips (`GigLine_Idle`, `GigLine_Walk`) and their import | +| `export_*`, `hero_facade_build`, `retail_frontage_build`, `theatre_trim_build`, `facade_kit_build` | Blender headless export (FBX); trim builders work in world coordinates | +| `ue_import_*`, `ue_hero_facade_import`, `ue_import_retail_detail` | Unreal headless import + placement | | `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) | | `ue_create_input_assets` | Enhanced Input actions + mapping context | | `render_screenshots.sh` | Screenshot capture pipeline | | `judge_screenshot.py` | Vision-model pre-check (optional) | | `input_emulation.py`, `input_test.py` | OS-level virtual keyboard/mouse injection for input verification | +| `Tools/_common.py` | Pure helper shared by Blender builders, importable without Blender (bay layout) | | `Tools/tests/` | Stdlib `unittest` tests for the pure parts of the tools (`requirements-tools.txt`) | | `Geo/tools/mapbox_buildings.py` | Second height source: Mapbox `mapbox-streets-v8` building data vs our OSM heights | @@ -454,6 +458,22 @@ Rule: place a building only against a neighbour or street mesh, never in isolati - **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI. +## ADR-8: Realistic building façades are a minor milestone, not a patch + +- **Context**: `0.1.4` carried the first façade pass as a _patch_, while the release policy says patches contain fixes and refinements and never new scope. The `0.1.6` retail pass then showed what that costs: a façade built in a patch has no release gate of its own, so a first pass that does not yet read as the building (`Screenshots/README.md`, 0.1.6 gaps) can sit inside a "done" patch series. Realism is also the project's leading quality goal, which a patch cannot carry. + +- **Decision**: Building realism gets its own minor milestone, `0.9.0 — Realistic buildings`, placed before the feature freeze. Its scope is the façade system (orders, profile library, storefront types, signage as decals), per-building photo-matched models, a measured material library with a texel-density rule, and LODs plus collision proxies. Patches keep fixes and refinements only. Renumbered by this decision: the former `0.9.0` freeze/beta/audit milestone becomes `0.10.0`, its patches `0.10.1`–`0.10.3`, and `1.0.0` moves to weeks 39–44. + +- **Consequences**: Façade work now has a release gate — every building measured against its own photograph, every building with a collision proxy and a LOD, no placeholder materials — so "modelled" means verified, not merely present. The price is that realism work stops being sliced into patches, and the schedule shifts by one minor cycle. Blockouts without a photograph stay blockouts and are listed as such, rather than being dressed up to fill the milestone. + +## ADR-9: Third-person body is an attachment of the first-person pawn, not a second pawn + +- **Context**: `0.2.0` needs a visible body and a third-person camera, while `0.1.0` shipped a first-person pawn with no body and the owner's decision was first person for that release. Two separate pawn classes would duplicate movement, input and reset logic, which is the code most likely to drift. + +- **Decision**: one pawn, both rigs. `AGigLineCharacter` carries a skeletal mesh (hidden in first person), a collision-tested spring-arm camera, and a **V** toggle that swaps the active camera and the body's visibility. The shipped default view stays first person; `-thirdperson` starts the pawn in the third-person rig and is what the screenshot run passes, so the review shot is reproducible without a human pressing a key. + +- **Consequences**: movement, reset, pause and the QA self-test stay in one place. The body's animation state is driven from `Tick` by speed rather than from input, so being pushed or falling animates correctly. The cost is that the camera swap is not a separate actor, so a future cinematic camera needs its own path rather than reusing the toggle. + # Quality Requirements ## Quality Requirements Overview @@ -490,6 +510,16 @@ Ordered by priority: realism first, performance last. The order decides design t > QS-9 is last on purpose. Performance is a floor that every release must meet, not the goal that shapes the content: realism decides what gets built, the frame budget decides how much of it. +### Building realism gate (0.9.0) + +| ID | Check | Metric | +| --- | --- | --- | +| BR-1 | A building with a reference photograph has a review shot compared against it | Silhouette and proportion deltas recorded per building in `Screenshots/README.md`; owner approval per shot | +| BR-2 | Every building is either photo-matched or explicitly recorded as an unfounded blockout | List in `TODO.md`; no building in an in-between state | +| BR-3 | Every building has a collision proxy and at least one LOD | Count of buildings missing either = 0 | +| BR-4 | No surface carries a placeholder material | Material audit in `Tools/ue_audit_scene.py` reports 0 placeholder slots | +| BR-5 | The realism pass does not break the floor | QS-9 still holds at 1440p, with the measured cost of the pass recorded | + # Risks and Technical Debts | ID | Risk / Debt | Priority | Mitigation | diff --git a/Game/GigLine/Content/Hollywood/DemoMap.umap b/Game/GigLine/Content/Hollywood/DemoMap.umap index 07b1424..793795e 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:398dd887438639e4152b2403b8b6c6358494f59911598110ec0da752f52851b7 -size 920353 +oid sha256:3cadf24b327116118550a7fb62dd6cc1cc748e152959ed2cdcf3a1de64bb4bb0 +size 781914 diff --git a/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Bronze.uasset b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Bronze.uasset new file mode 100644 index 0000000..3017713 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Bronze.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4b1ebcefdb8b02bf6f3dfc61f25ceed6bde1e5ecf40067437fd4d70f7a93c45 +size 56705 diff --git a/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Burgundy.uasset b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Burgundy.uasset new file mode 100644 index 0000000..3b32fd8 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Burgundy.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:682a4a2e3d365daa848fb3c8f8c902a1ccd181fc68120fe72452449337c95dc6 +size 56702 diff --git a/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Glass.uasset b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Glass.uasset new file mode 100644 index 0000000..2311b01 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Glass.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aedc0b3c2b40ead88ddda2e1bfdd8fe7047fc044255c57a4be1a736c329332bd +size 56688 diff --git a/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Mural.uasset b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Mural.uasset new file mode 100644 index 0000000..377e376 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Mural.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b2688239130bb6210829d2d1a39b9ef4da74eb06f4fdfacae08faf2dc7e7e6b +size 56685 diff --git a/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Stucco.uasset b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Stucco.uasset new file mode 100644 index 0000000..2c9b5fd --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_Stucco.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf6cbf3830fba67080af03dca25da41054ef6d7ef22fc88da9e67b898c9ff124 +size 56681 diff --git a/Game/GigLine/Content/Hollywood/Frontage/M_Retail_White.uasset b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_White.uasset new file mode 100644 index 0000000..24a99b5 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/M_Retail_White.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd9698b0577e35d145117f049777fd3218d602cff0f78c8ca7fa7965d08ad8ad +size 56694 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_Retail_6806.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_Retail_6806.uasset new file mode 100644 index 0000000..9095828 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_Retail_6806.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d436f4ed1e6d1b922ab93b61d801296453e755133039cb68a24a768cf80fd9eb +size 93500 diff --git a/Game/GigLine/Content/Hollywood/Frontage/SM_Retail_LeeDrug.uasset b/Game/GigLine/Content/Hollywood/Frontage/SM_Retail_LeeDrug.uasset new file mode 100644 index 0000000..43664c0 --- /dev/null +++ b/Game/GigLine/Content/Hollywood/Frontage/SM_Retail_LeeDrug.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48acd6aa22718c98df68b913b0e9a1d258b33ccf9a59ba76439d5cbeaf7c3137 +size 96341 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset b/Game/GigLine/Content/Hollywood/Site/M_Asphalt.uasset index 157a8b9..fa2a3e5 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:626f06fc4414da21c4eb9f8b8405ce5c9562ab05c4751284032d243cdf7efc62 +oid sha256:fcf355af961bd5f0c50d9dd5f9f680e64ad23327cd4934831a0c23644b3af20c size 10019 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_Concrete.uasset index 9f58ea3..10f810e 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:2e7a6442f798082dcc48aebbfdace850f4b7ead3223a6b0ef84739bc5811536d +oid sha256:fe5db22b218b38420b546ee75e1f10128f3735ff9679ec42350405cc741bc55f size 10024 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeBanner.uasset index eb9a041..ea83fb9 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:95ca803600002e4acb6e0b628909aa3270364e6ed8becf2fa0243a49bc7bd345 +oid sha256:8396d7101a43df58b845b2c9fdf745c11ed8aa8d2703f603fde038ae37472765 size 11419 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeGlass.uasset index f8f836c..fe9c91f 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:adbb4c080f83299deefd71d25d5a537f4def4e1510a65b2c7045f954e70c5ae0 +oid sha256:c07c6cd2ebe7f984196757043e6c16a092a154b7dae33b8c3f11113a78e42fd2 size 11414 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStone.uasset index 318bd87..810d776 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:3dc99df04a741567746a2ad362bdd433bd8bda6e9c0879725a5fa2c6c86a659d +oid sha256:3229e39839dfd7cfbe787cddc4337399ecfb7bc25c1364057db38cfd34d07c5b size 17083 diff --git a/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset b/Game/GigLine/Content/Hollywood/Site/M_FacadeStoneShadow.uasset index c040e40..de97c69 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:c3bcdcc651218046e55705a364afd81bc8371bfefe04f075f2f02ff7f918fa62 +oid sha256:8dd4749d4946a34cd333320d098887843040765651273e336804b819880994ee size 17113 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset b/Game/GigLine/Content/Hollywood/Site/M_Ground.uasset index abb47ed..c23ffe8 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:80e71916874884aba171b27d38234425addb639bd97b9f1eef7bbe7d8b116e8a +oid sha256:a9c8cc98384edbe2df0ab772f790310047339159346fb11ad4c7b86a82bea132 size 10014 diff --git a/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset b/Game/GigLine/Content/Hollywood/Site/M_KerbConcrete.uasset index a86891d..0b90a35 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:e0842e3edca0ac69699c254c7deef8d4b961186267bc5803c0992d0cc5c1f253 +oid sha256:c6934516f42a8539d7a9e37b4386a3746be9eec4cb18feabe433d1126e9cf4c4 size 10044 diff --git a/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset b/Game/GigLine/Content/Hollywood/Site/M_RoadPaint.uasset index 0ec07c8..0d2b0d7 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:76727955f3d1564a691cb91392d9376a48145facc146814e8dd95f325c21fd61 +oid sha256:ad2e8ec104f49d612d66b54e7af742ac1041b9dd8572083c252b7011c0438101 size 10029 diff --git a/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset b/Game/GigLine/Content/Hollywood/Site/M_Stucco.uasset index 434ad4c..a9067fe 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:772d2b3059235ec626df8f3e425de90911a336b70895982e348eecb643b5d748 +oid sha256:a09613d3a4f47fb6532e402061acc3510265896b3bff90167943387a32c5addc size 11389 diff --git a/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset b/Game/GigLine/Content/Hollywood/Site/Terrain_DEM.uasset index e7af187..4759c46 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:d7a3d68541dbdef1c5b2ffdefb3aba23ed79709defc8546106bd98a86d01711d +oid sha256:cdd2cde3bfdbc117cd0445d61c68659f8924ed794df9e61cedc466b5bb97c1ae 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 440051c..01702ac 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:8d7f5c0e1967d623451e655493f471ec86323ee649e7bb5d092f72ad2fdd4a51 +oid sha256:b1e00754bcb7fb131a40c9a162bf0dfc0c1a0b9a84718e121e715ed4f2d4d9ab 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 3d7c3ee..06d4b3b 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:539e57ac63cae32e62dcbff77418a4d50cda886d8f87045a2e6b5a1f1cd71851 +oid sha256:2f717ded45c8aaf50861582119ce524061947bf05a36f7e335f657f193f5c6e0 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 11f174b..d1db955 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:70540e59b0919d888bb212d5756b2ef7821ade588812300ccfeaf877dbadace8 +oid sha256:b6ed6dcdcacec001b0407ac7b06d7a28244a0b95ebc6ff1592047b7a2f25f8dc 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 7bfade4..79e20bc 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:fc0ed1a910138ab847297ebcaf1dbce03392ddb9903d7a5120308e3f73a114e5 +oid sha256:9e5a34257578275ea6943266ab22676e0da79945fb9f49f581620786564ebe3b 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 d19395c..8321fd1 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:12f3c6a4a0d048930c9d344b681c18d0b4c6c7d6f8a2e6b4eaef92efbaaf66c9 +oid sha256:e03c186726d7384eec66a3cd4ded3f6c907b1c64793a2daa999a5558c61c053e 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 d7d331d..9ebb467 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:3a0712f6b56a9c793a4f49a234a756e254c5a6b4a8818ebd28907ad6671defbe +oid sha256:ac2fce195bfc270b86810c906bc37dae592797fa4c3cf4fa02a005dfb26db446 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 3042726..8c4d274 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:3a38924f29f51aa590a9b5254bdb42fe1dd59549ef5642dd44a411080e5b4c6b +oid sha256:ad13b6b085fafb13723adf631d4fdd0b1dd4a118d50607d8d87af5d67f5d1921 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 ab29847..1e69c24 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:2b219d05d28536b133f0a5c7410d0c761ede74d59e25688a456157023c31679d +oid sha256:1a17fc39a2a9b8b0e0f5103857876173a766c9db26e3334bd5d2b0e06657550c 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 fbd8c25..3d1dc5e 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:6ac53545672dba8dcda41de150c697881a36afe8189135e0c35b198e9e290a4d +oid sha256:36649be3dd41c043f68494ac8dbcfd2bb85e735f8665ce1e3c45f2672a109d3e 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 9799d75..ad862cd 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:650363207b4e45dedcb57b417e622a6519dd5a5d73c29d38f2a9959a69f3d006 +oid sha256:affc4a3580c12d372f03bfcaae78543739bf4e8cb5d234b1c2273357b1451035 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 ea7fb4d..069a7fa 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:c2974250c1ab4cc9c1d96606fe72db2c724ec64f609f65282004a3ac2e766efe +oid sha256:eb80f9f2267837bbe796f77a7d5564ef9928436709d656cd0b6ce989ee3a065b 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 3572385..7334a32 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:3c4fd804f9f221a8c3ff8059ef96cf652483ec2da8b874f4411adb651a8ae2fc +oid sha256:133969c52a67672ef871a423ca4e445f65fce751ec6c275f63b6855300f6774b 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 4e25953..14a16d9 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:12eeeb9161b935b10448007c7074a0583592fa39b126b5862d632b8ce83d44fb +oid sha256:ff64d40901f3b376446ed2de3bebab4f93317e6ae3fc89fe6ab6c9db1d30efe5 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 b22af2f..c931cdf 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:ea03175fb0e2d5585f810e69327832419b1846d0eeaedbfd6ef9c0c5769236f1 +oid sha256:c84cbf3f0afd1f2f18d345c61a748a2fa5f6fc033df6b238f8edcd59c8e8cf8c size 72413 diff --git a/Game/GigLine/Content/Test/ReviewMap.umap b/Game/GigLine/Content/Test/ReviewMap.umap index 51fe182..f9adb5d 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:838fd0b14285804358f8779169e2caad6a35235c75fee58e11936e8a9119eeef -size 29389 +oid sha256:ec72b33cc57c5aa50889c6d0bc68c78ce5fde621881ed5c3bde4d6679610ef4e +size 28811 diff --git a/MEMORY.md b/MEMORY.md index de67cf7..9396193 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -523,3 +523,11 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. - 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. + +## 2026-11-09 — Retail frontage and two pipeline traps + +- **`Tools/render_screenshots.sh` called `./Engine/...` for the capture step** while every commandlet above went through `run_cmdlet`'s `cd "$ENGINE"`. With `set -e` the script died at exit 127 *after* rebuilding the maps, so the PNGs silently stayed a day old and `--verify` never ran. Fixed to `"$ENGINE/Engine/Binaries/..."`. Symptom to remember: maps rebuild, screenshots do not move. +- **The generic façade kit is applied to every actor labelled `Frontage_*` or `North_*`**, so staging a hand-modelled façade under those labels gets the kit pasted over it — two contradicting façade languages on one wall. `ue_apply_facade_kit.py` now skips `MODELLED` labels, and retail trim stages as `RetailTrim_*`. +- **A Blender builder cannot be imported by a unit test** (`import bpy` at module scope). Pure layout arithmetic lives in `Tools/_common.py`; `Tools/tests/test_retail_frontage.py` tests it there, and the builder delegates. Same trick `fbx_export_preset.py` uses for its preset dicts. +- **Joining 85 parts into one mesh per building** before export cut the Unreal import from 85 assets to 2, and the staging step to two spawns. Record per-part dimensions *before* the join: after it only the joined bounds exist. + diff --git a/PLAN.md b/PLAN.md index a4ad367..2128e40 100644 --- a/PLAN.md +++ b/PLAN.md @@ -108,8 +108,9 @@ Solo-development estimate: **approximately 30–40 weeks**, subject to asset qua | `0.6.0` | 22–24 | Visual/audio pass and evening lighting preset | `0.6.1` lighting fixes; `0.6.2` material corrections; `0.6.3` audio balancing | | `0.7.0` | 25–27 | Settings, control rebinding, save/load, accessibility basics | `0.7.1` save fixes; `0.7.2` UI fixes; `0.7.3` input persistence fixes | | `0.8.0` | 28–30 | Performance budgets and broader Linux hardware testing | `0.8.1` VRAM reductions; `0.8.2` frame-time fixes; `0.8.3` compatibility fixes | -| `0.9.0` | 31–34 | Feature freeze, public beta, license and build audit | `0.9.1` blocker fixes; `0.9.2` regression fixes; `0.9.3` documentation fixes | -| `1.0.0` | 35–40 | Stable neighborhood-scale walking/driving release | `1.0.x` maintenance and compatibility fixes | +| `0.9.0` | 31–34 | Realistic buildings: façade system, per-building models, measured material library, LODs and collision proxies | `0.9.1` façade fixes | +| `0.10.0` | 35–38 | Feature freeze, public beta, license and build audit | `0.10.1` blocker fixes; `0.10.2` regression fixes; `0.10.3` documentation fixes | +| `1.0.0` | 39–44 | Stable neighborhood-scale walking/driving release | `1.0.x` maintenance and compatibility fixes | Finish each milestone's release checks before expanding scope. Cut map size and simulation complexity before compromising the core walking/driving experience. diff --git a/ROADMAP.md b/ROADMAP.md index 00c8232..8c9c237 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), **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). +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), **`v0.1.6` released 2026-11-09**: retail frontage for 6800 Lee Drug and 6806–6820 modelled from their own photograph; 6822 Budget deferred (no reference) and the lettering/parapet detail moved to `0.9.0`. **Next:** `0.2.0` (player body, third-person camera, animation). | Area | State | | --- | --- | @@ -168,22 +168,38 @@ Workstation, geo pipeline, and engine validation are done. The art pipeline has **Goal:** fix compatibility issues from the hardware testing matrix. -## 0.9.0 — Feature freeze, public beta, license and build audit (weeks 31–34) +## 0.9.0 — Realistic buildings (weeks 31–34) + +**Goal:** every building in the demo area reads as the building it is, at walking distance, judged against its own photograph — and where no photograph exists it stays an honest blockout rather than an invented façade. This is the milestone that turns the façade system from "a kit applied to volumes" into architecture: a shared bay-and-profile vocabulary, a material library with measured albedo and roughness, modelled storefronts and signage, and per-building reference matching with a measured tolerance. + +**Scope:** + +- A façade system that carries more than one order: Art Deco, neoclassical and streamline-moderne variants on the same 4.96 m bay module, with a profile library for cornices, copings, string courses and storefront surrounds. +- Per-building façades for the six south-side buildings and the two north-side buildings inside the 50 m buffer, each from its own reference set; the hero keeps its existing model and gains the detail levels this pass defines. +- Storefronts and signage as first-class geometry plus decals: mullions, bulkheads, recessed entries, awning and canopy brackets, painted wall lettering, blade and projecting signs. Wall lettering is a texture/decal problem, not a block of geometry — the `0.1.6` attempt proved plates do not read. +- A material library with measured values per surface type (stucco, painted render, ashlar, brick, terrazzo, glazing, bronze and steel trim) and a texel-density rule, replacing per-building ad-hoc colours. +- LOD chain and collision proxies per building, so realism does not cost the 60 FPS floor. + +**Out of scope:** interiors, night lighting and audio (the `0.6.0` evening preset owns those), vehicles, pedestrians, and any change to footprints or massing — OSM stays the source for those. + +**Release gate:** each building has a review shot compared against its own photograph with the silhouette and proportion deltas recorded in `Screenshots/README.md`; no building carries an unverified façade; every building has a collision proxy and at least one LOD; 60 FPS at 1440p holds; no surface is left with a placeholder material; `THIRD_PARTY_NOTICES.md` covers every photographic reference used. + +## 0.10.0 — Feature freeze, public beta, license and build audit (weeks 35–38) **Goal:** freeze features, produce a public beta for testers, and complete the audits: licences for `THIRD_PARTY_NOTICES.md` and all MPFB, texture, animation, vehicle, and audio assets; Epic distribution requirements; and a fresh checkout that builds and packages reproducibly. -## 0.9.1 — Blocker fixes (patch) +## 0.10.1 — Blocker fixes (patch) **Goal:** fix blockers reported during the public beta. -## 0.9.2 — Regression fixes (patch) +## 0.10.2 — Regression fixes (patch) -**Goal:** fix regressions introduced by `0.9.x` fixes. +**Goal:** fix regressions introduced by `0.10.x` fixes. -## 0.9.3 — Documentation fixes (patch) +## 0.10.3 — Documentation fixes (patch) **Goal:** update all `Docs/` for accuracy before `1.0.0`. -## 1.0.0 — Stable neighborhood-scale walking/driving release (weeks 35–40) +## 1.0.0 — Stable neighborhood-scale walking/driving release (weeks 39–44) **Goal:** a final acceptance sweep across all `0.x` features, confirmation that the agreed scope (neighbourhood-scale walking and driving) is met, and final build, documentation, and licensing verification. `1.0.0` marks the stable scope, not completion of a whole city. diff --git a/Screenshots/README.md b/Screenshots/README.md index 043a5c6..b4f9407 100644 --- a/Screenshots/README.md +++ b/Screenshots/README.md @@ -22,7 +22,8 @@ Every shot is embedded below, in order, so the whole review happens in this file | 2 | [Street level](#2-street-level) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 3 | [Crossing](#3-crossing) | ☑ 2026-11-09 | ☑ 2026-11-09 | | 4 | [North buildings](#4-north-buildings) | ☑ 2026-11-09 | ☑ 2026-11-09 | -| 5 | [Third-person body](#5-third-person-body) | ☑ 2026-11-09 | ☐ pending | +| 5 | [Retail frontage](#5-retail-frontage) | ☑ 2026-11-09 | ☑ 2026-11-09 | +| 6 | [Third-person body](#6-third-person-body) | ☑ 2026-11-09 | ☐ pending | **Verified** is what `--verify` reports: the frame is not blank, is the right size, and has tonal range. **Approved** is the owner's verdict, recorded here. Each shot also has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) describing the content it must show. @@ -72,7 +73,28 @@ Every shot is embedded below, in order, so the whole review happens in this file --- -## 5. Third-person body +## 5. Retail frontage + +![Retail frontage: 6800 Lee Drug and 6806–6820 from across the street](demo/review-demo-retail-frontage.png) + +- **File:** `demo/review-demo-retail-frontage.png` +- **View:** north sidewalk, level horizon, 68° lens +- **Expected content:** the retail run beside the hero — 6800 Lee Drug's bronze cornice, mural row and parapet next to 6806–6820's burgundy fascia, pilasters and window storey +- **Verified:** ☑ 2026-11-09 · **Approved:** ☑ 2026-11-09 + +Read this one against the reference photograph it was modelled from: `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA 4.0). + +Known gaps in this first pass, recorded rather than hidden: + +- **The parapet still reads as a blank band.** It is now two tiers with three deeper reveals, and the step is visible, but there is no profile or shadow line to break the wall — a moulded coping and a modelled reveal need the `0.9.0` façade system. +- **The mural row now reads** (framed plates standing 0.12–0.20 m off the wall), but **the lettering band does not**: it is a plate at 0.10 m and it disappears at 40 m. Wall lettering is a decal/texture problem, scoped in `0.9.0`; no amount of extra plate depth fixed it in the second pass. +- **The corner tower is a plain cylinder with two cap rings** — the stepped Deco profile in the photograph is richer than that. +- **The camera crops the east end** of Lee Drug at 68° from the north sidewalk. +- **6822 Budget is untouched** and still a bare blockout volume: no photograph of it exists. + +--- + +## 6. Third-person body ![Third-person body: the player pawn seen from behind on the north sidewalk](demo/review-demo-third-person.png) @@ -115,16 +137,19 @@ Every issue is tracked in `TODO.md` under the version that will fix it; an issue | Issue | Where it is tracked | State | | --- | --- | --- | | Hero stone renders pale — the material is a procedural graph and a generated texture set, not a measured surface | `0.9.0` → measured material library | Open | -| Hero façade has no doors at street level left and right of the central bay, and the central-bay elements are not positioned as the photographs show | `0.9.0` → per-building models, hero façade | Open | -| Third-person body renders in bind pose: the clip is bound and `PlayAnimation` is called, but the pose does not apply | `0.2.0` → verify animation plays correctly | Open (blocking `0.2.0`) | +| Hero façade has no doors at street level left and right of the central bay, and the central-bay elements (columns, stairs, set-back entrance, door frames, signage) are not positioned as the photographs show | `0.9.0` → per-building models, hero façade | Open | +| Nothing may stand in front of the hero façade that blocks it — no awning, no temporary structure | `0.9.0` → per-building models, hero façade | Open | +| Retail parapet has no moulded profile, and wall lettering built as a plate does not read at 40 m | `0.9.0` → 6800 Lee Drug and 6806–6820, second pass | Open (moved out of `0.1.6`) | +| 6822 Budget is a bare blockout volume — no photograph exists to model it from | `0.9.0` → photography gap | Deferred (moved out of `0.1.6`) | +| Third-person body renders in bind pose: the clip is bound and `PlayAnimation` is called, but the pose does not apply | `0.2.0` → verify animation plays correctly | Open (blocks `0.2.0`) | | Camera collision is implemented but never exercised against a wall | `0.2.0` → camera collision | Open | -| Between kerb and building line the walkable surface is the terrain mesh, not a modelled pavement | `0.6.0` → visual polish | Open | +| Between kerb and building line the walkable surface is the terrain mesh, not a modelled pavement | `0.6.0` → model the pavement between kerb and building line | Open | Accepted by the owner, not a defect: - Banners hang on the column line rather than flush to the wall — that is how they hang in the photographs. -Resolved and removed: +Resolved and removed from this list on 2026-11-09: - "The El Capitan Theatre has no marquee or blade sign" — `Tools/ue_stage_theatre_trim.py` stages all six trim actors; verified in `/tmp/gigline/theatre-import.txt`. -- "Materials are not yet created in Blender or fully integrated" — contradicted by the scene conventions above. +- "Materials are not yet created in Blender or fully integrated" — contradicted by the scene conventions above: every surface carries a look-dev material built in `Tools/ue_build_demo_map.py`. diff --git a/Screenshots/demo/review-demo-crossing.png b/Screenshots/demo/review-demo-crossing.png index f742139..457233d 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:a818f7d2f3e985e5a0a6634ccde4b8651256c2621094bb62a406a055bf0241e4 -size 3086727 +oid sha256:06bd939e40544e818c721a8e52d4f9db42bcc15578fdf23a0f07e5e3e81a3a0e +size 3090121 diff --git a/Screenshots/demo/review-demo-north-buildings.png b/Screenshots/demo/review-demo-north-buildings.png index 9d307cd..a7d947d 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:3e0b98b21a3b6aba6401d9f8ff2a8a8a5affeef41b468ced5600ccfcb7b99fbc -size 2619257 +oid sha256:16b30099d71573954ae5f2bf3082a50b6ef715753c83470b6d56146b93da8526 +size 2619302 diff --git a/Screenshots/demo/review-demo-retail-frontage.png b/Screenshots/demo/review-demo-retail-frontage.png new file mode 100644 index 0000000..c5854ac --- /dev/null +++ b/Screenshots/demo/review-demo-retail-frontage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9cb8296dd77c3b1ce3af3c26be36280f22bdcbf35c79458cd82850ee4083510 +size 2520582 diff --git a/Screenshots/demo/review-demo-spawn-view.png b/Screenshots/demo/review-demo-spawn-view.png index 2fb3640..92bfc1c 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:c70565745e24d5b3ab566e8a6b91d574b294f62b27341219b88ccfbd3da451ea -size 2931773 +oid sha256:c060b014cf4e04d0f5b3c8255e505a1f0d80abb1a1796190a7aba00370d49618 +size 2944637 diff --git a/Screenshots/demo/review-demo-street-level.png b/Screenshots/demo/review-demo-street-level.png index cf53da1..24d4553 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:45da3c12c0056fa4d7c6a48d7a41173e1bfe9f2e5642c49f27040cd84e8c7936 -size 2848284 +oid sha256:ee9182712bef9ea7415722e2c546df0ee3d914cc46ee73b450c683245b1f4a3c +size 2875166 diff --git a/TODO.md b/TODO.md index 3aa1e81..c7b9b10 100644 --- a/TODO.md +++ b/TODO.md @@ -9,16 +9,6 @@ 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.6 — Retail frontage (patch) - -Suggested blender skills: **references** (reference-image-match), **archviz**, **historical-worlds** for the 1920s–1990s retail mix on this run. - -### Patch tasks - -- [ ] **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) ### Character setup - active - session-5598ce7e-27e3-4c64-bb49-c04a4a31ee58 (branch `anim-body-0.2.0`) @@ -182,6 +172,7 @@ Suggested blender skills: **references** (reference-image-match), **archviz**, * - [ ] Create an evening lighting preset. - [ ] Complete a visual polish pass across the neighborhood. +- [ ] Model the pavement between kerb and building line; today that strip is the terrain mesh, which reads as bare ground under the shopfronts (`Screenshots/README.md`). Carried over 2026-11-09. - [ ] Add ambient audio (city ambience, traffic, basic SFX). - [ ] Verify licenses for all added audio files. @@ -285,7 +276,49 @@ Suggested blender skills: **references** (reference-image-match), **archviz**, * - [ ] Fix compatibility issues from the hardware testing matrix. - [ ] Tag `v0.8.3` and write `CHANGELOG.md` entry; then remove this section. -## 0.9.0 — Feature freeze, public beta, license and build audit (weeks 31–34) +## 0.9.0 — Realistic buildings (weeks 31–34) + +Suggested blender skills: **archviz** (real-world scale, façade composition, elevation read), **references** (reference-image-match and visual-match checklists — every building is judged against its own photograph), **procedural-modeling** + **geometry-nodes** (the façades system: bay module, cornice and coping profiles, storefront types as reusable node groups), **materials** (measured albedo and roughness per surface type), **texture-workflow** (baked AO/curvature/normal, decals for painted lettering and signs), **uv-workflow** (texel density and modular UVs), **lod-pipeline** (LOD chain per building), **collision-proxy** (UCX proxies so detail geometry is not the collider), **lookdev** (screenshot comparison loop), **camera-cinematography** (reproducing each building's reference angle), **asset-optimization** + **unreal-export** (pre-export validation, `ue_metric_v1`), **qa-review** (the gate before every review shot). + +### Façade system + +- [ ] **Extend the module kit beyond one order.** Art Deco, neoclassical and streamline-moderne variants on the shared 4.96 m bay: alternate window proportions, spandrel and transom bands, and the way each order terminates at the roof. +- [ ] **Build a profile library** for cornices, copings, string courses, sills and storefront surrounds, so a building is assembled from named profiles rather than ad-hoc boxes. +- [ ] **Storefront types**: recessed entry, bulkhead and kickplate, mullion spacing, transom, awning/canopy brackets, and grille/shutter variants. +- [ ] **Signage and lettering as decals.** Painted wall lettering, blade signs and fascia panels become texture and decal work with a documented projection; `0.1.6` proved that lettering built as geometry plates cannot resolve at review distance. +- [ ] Document the system in `Art/blender/README.md` and the arc42 building block view. + +### Per-building models + +Each building needs its own reference set; where none exists the blockout stays and the item says so. + +- [ ] Hero façade (6840): raise to this pass's detail level — capitals and fluting, window rhythm behind the colonnade, parapet motto band and medallions. +- [ ] Hero façade (6840), entrance: **doors at street level left and right of the central bay**, as the photographs show, and correct positioning of the central-bay elements (column bases, entrance stairs, set-back entrance, door frames, banners and signage). Nothing may stand in front of the façade that blocks it — no awning, no temporary structure. Carried over from `Screenshots/README.md`, 2026-11-09. +- [ ] El Capitan Theatre (6838): marquee, blade sign, entrance and the theatre's own elevation from its 18-frame set. +- [ ] 6800 Lee Drug and 6806–6820: second pass on the `0.1.6` trim. Moved here from that patch because geometry cannot close them: **wall lettering as a decal** (a plate at 0.10 m still vanishes at 40 m), a **moulded parapet profile** instead of a two-tier box, richer **corner-tower stepping**, and the portrait panels reworked as textures rather than framed plates. +- [ ] **Photography gap — 6822 Budget** (deferred out of `0.1.6`, blocked): no photograph exists; the single-storey 6.0 × 5.5 m volume cannot be read from the neighbour shot, so it stays a bare blockout until references are acquired. Record the sources checked in `Geo/`. Also re-shoot 6806–6820 and 6800 from the south sidewalk: the only reference is one oblique with a bus across Lee Drug's shopfront and a car across 6806's. +- [ ] 6902, 6922 and the remaining south frontage. +- [ ] North side: TCL Chinese Theatre and Madame Tussaud's façades from their reference sets. +- [ ] Reconcile every modelled height against `Geo/tools/block_height_report.py` and record the source per building. + +### Materials, UVs and performance + +- [ ] **Measured material library**, including the hero's stone, which currently renders pale because it is a procedural graph and a generated texture set rather than a measured surface: stucco, painted render, ashlar, brick, terrazzo, glazing, bronze and steel trim, with albedo and roughness taken from the photographs rather than guessed; one Unreal material instance per surface type. +- [ ] **Texel-density rule** per building class, applied through modular UVs; lightmap UVs where required. +- [ ] **LOD chain and collision proxies** per building: UCX proxies for blocking volume, detail geometry non-colliding. +- [ ] Re-validate the 60 FPS floor at 1440p once the detail lands, and record the cost of the pass. + +### Release checks + +- [ ] Every building with a photograph has a review shot measured against it, with silhouette and proportion deltas recorded in `Screenshots/README.md`. +- [ ] No building carries an unverified façade; blockouts without references are listed as such in `TODO.md`. +- [ ] Every building has a collision proxy and at least one LOD. +- [ ] No surface left on a placeholder material. +- [ ] `THIRD_PARTY_NOTICES.md` covers every photographic reference used in this pass. +- [ ] Performance target still met at 1440p. +- [ ] Tag `v0.9.0` and write `CHANGELOG.md` entry; then remove this section. + +## 0.10.0 — Feature freeze, public beta, license and build audit (weeks 35–38) ### Freeze and audit @@ -297,29 +330,29 @@ Suggested blender skills: **references** (reference-image-match), **archviz**, * ### Release checks -- [ ] Beta feedback triaged; blockers logged for `0.9.x`. -- [ ] Tag `v0.9.0` and write `CHANGELOG.md` entry; then remove this section. +- [ ] Beta feedback triaged; blockers logged for `0.10.x`. +- [ ] Tag `v0.10.0` and write `CHANGELOG.md` entry; then remove this section. -## 0.9.1 — Blocker fixes (patch) +## 0.10.1 — Blocker fixes (patch) ### Patch tasks - [ ] Fix blockers reported during the public beta. -- [ ] Tag `v0.9.1` and write `CHANGELOG.md` entry; then remove this section. +- [ ] Tag `v0.10.1` and write `CHANGELOG.md` entry; then remove this section. -## 0.9.2 — Regression fixes (patch) +## 0.10.2 — Regression fixes (patch) ### Patch tasks - [ ] Fix regressions introduced by `0.9.x` fixes. -- [ ] Tag `v0.9.2` and write `CHANGELOG.md` entry; then remove this section. +- [ ] Tag `v0.10.2` and write `CHANGELOG.md` entry; then remove this section. -## 0.9.3 — Documentation fixes (patch) +## 0.10.3 — Documentation fixes (patch) ### Patch tasks - [ ] Update all `Docs/` for accuracy before `1.0.0`. -- [ ] Tag `v0.9.3` and write `CHANGELOG.md` entry; then remove this section. +- [ ] Tag `v0.10.3` and write `CHANGELOG.md` entry; then remove this section. ## 1.0.0 — Stable neighborhood-scale walking/driving release (weeks 35–40) diff --git a/Tools/README.md b/Tools/README.md index 63b1b04..23705dd 100644 --- a/Tools/README.md +++ b/Tools/README.md @@ -17,7 +17,10 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b | --- | --- | | `check-lfs.sh` | Fails if the `lfs` filter is missing or an LFS-tracked file was committed as a plain blob | | `facade_kit_build.py` | Builds the reusable façade **module kit** at the measured 4.96 m bay: pilaster, sill, lintel, window bay (surround + glazing), shopfront bay (piers, glass, fascia), door bay (jambs, door, threshold), cornice and parapet + coping. Exports each piece with the shared preset plus a JSON part report (dims + evidence per piece). | +| `ue_import_retail_detail.py` | Imports the two retail trim meshes into `/Game/Hollywood/Frontage` with complex-as-simple collision — the trim decorates a solid the player already collides with, so it must not add snag points. Staged by the demo builder as `RetailTrim_*`. | | `ue_import_facade_kit.py` | Imports the kit into `/Game/Hollywood/Kit/*` as static meshes with complex-as-simple collision; writes `/tmp/gigline/kit-import.txt`. | +| `retail_frontage_build.py` | Retail frontage trim for **6800 Lee Drug** and **6806–6820** (0.1.6): bronze cornice, mural row, lettering band, stepped Deco parapet and rounded corner tower on Lee Drug; burgundy fascia, pilasters, white-framed windows and banded parapet on 6806–6820. Built in world coordinates from the blockout bounds, one joined mesh per building, JSON part report with the evidence behind every piece. `6822 Budget` is **not** built — no photograph exists. | +| `retail_frontage_build.py` | Retail frontage trim for **6800 Lee Drug** and **6806–6820** (0.1.6): bronze cornice, mural row, lettering band, stepped Deco parapet and rounded corner tower on Lee Drug; burgundy fascia, pilasters, white-framed windows and banded parapet on 6806–6820. Built in world coordinates from the blockout bounds, one joined mesh per building, JSON part report with the evidence behind every piece. `6822 Budget` is **not** built — no photograph exists. | | `theatre_trim_build.py` | El Capitan Theatre trim: projecting marquee canopy, illuminated fascia, recessed soffit, vertical blade sign with frame and bracket. Built **in world coordinates** (span and pavement height baked in) with a JSON part report, so staging is a single spawn at the origin. | | `ue_stage_theatre_trim.py` | Imports the trim to `/Game/Hollywood/Theatre` and stages one actor per piece in the DemoMap with site materials; clears a previous run first. | | `ue_apply_facade_kit.py` | Lays the kit over a building's street face from its own bounding box: bay count = round(width / 4.96), storeys from the height, shopfronts on the ground floor, windows above, pilasters on bay boundaries, cornice and parapet at the top. Clears a previous application first; unique labels per building. | diff --git a/Tools/_common.py b/Tools/_common.py new file mode 100644 index 0000000..d1e4ac5 --- /dev/null +++ b/Tools/_common.py @@ -0,0 +1,27 @@ +"""Shared helpers that must stay importable outside Blender and Unreal. + +Blender-side builders import `bpy` at module scope, so nothing in them can be unit +tested. Pure layout arithmetic lives here instead: the builders import it, and +`Tools/tests/` imports it without a 3D application. + +Standard library only. +""" + +from __future__ import annotations + + +def bay_centres(x0, x1, margin=0.9, bay=2.2): + """Centres of equal bays across a frontage, inset by `margin` at each end. + + Used by the façade kit and the retail frontage so both use one rhythm. The count + is the span divided by the bay module and rounded, so a 33 m frontage at a 2.2 m + module gets 14 bays rather than one bay per 2.2 m of wall with a stub at the end. + + Returns [] when the margins consume the whole frontage. + """ + span = (x1 - x0) - 2.0 * margin + if span <= 0: + return [] + count = max(1, int(round(span / bay))) + step = span / count + return [x0 + margin + step * (i + 0.5) for i in range(count)] diff --git a/Tools/render_screenshots.sh b/Tools/render_screenshots.sh index 6af627f..f30de36 100755 --- a/Tools/render_screenshots.sh +++ b/Tools/render_screenshots.sh @@ -39,9 +39,9 @@ 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 "== 6/6 capture the demo map ==" -# Absolute engine path: this step runs from the repo, not from $ENGINE, and a bare -# ./Engine/... failed with "No such file or directory" while every commandlet above -# succeeded through run_cmdlet's cd. -thirdperson so the review shows the body. +# The engine path is absolute: this step runs from the repo, not from $ENGINE, and a +# bare ./Engine/... failed with "No such file or directory" while every commandlet +# above succeeded through run_cmdlet's cd. "$ENGINE/Engine/Binaries/Linux/UnrealEditor-Cmd" "$PROJ" /Game/Hollywood/DemoMap \ -game -windowed -ResX=1920 -ResY=1080 -nopause -nosplash -thirdperson -stdout diff --git a/Tools/retail_frontage_build.py b/Tools/retail_frontage_build.py new file mode 100644 index 0000000..5736dcd --- /dev/null +++ b/Tools/retail_frontage_build.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python3 +"""Retail frontage trim for 6800 Lee Drug and 6806–6820 (0.1.6). + +Both buildings are bare OSM extrusions with a flat stucco material; what makes them +read as *the* retail run beside the hero is the façade layer: the bronze cornice and +stepped Deco parapet on Lee Drug, and the pilaster-and-fascia rhythm on 6806–6820. + +The trim is built **in world coordinates** in the shared frame (metres, X east, +Y north, Z from the project datum), so the export carries its position and the +Unreal staging step is a single spawn at the origin — the same convention as +`Tools/theatre_trim_build.py` and `Tools/export_block_frontage.py`. + + blender -b --factory-startup --python Tools/retail_frontage_build.py -- \ + --out /tmp/gigline/retail --report /tmp/gigline/retail-report.json + +Bounds are **measured**, not assumed: they are the blockout objects' world bounds +(`Lee Drug Building`, `element.019`), which sit at Z 0 with the volume top at 12.10 m. +Vertical proportions come from the single reference photograph and are recorded with +the assumption in `Art/blender/retail/ref-analysis.md`. + +6822 Budget is deliberately **not** built: there is no photograph of it, and the TODO +item says so. Guessing its façade would be the misattribution the item warns against. +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import sys + +import bpy + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import _common # noqa: E402 +import fbx_export_preset as fbx_preset # noqa: E402 + +# --- measured bounds, from the blockout (world metres) ------------------------ +# Lee Drug 6800, OSM way 424024149. Front face is the north edge (max Y). +LD_X0, LD_X1 = -33.20, -14.37 +LD_FACE_Y = -14.63 +LD_H = 12.10 + +# 6806-6820, OSM way 424024150. +B6_X0, B6_X1 = -66.08, -33.07 +B6_FACE_Y = -12.52 +B6_H = 12.10 + +# --- proportions, read off the reference photograph --------------------------- +# Fractions of the 12.10 m height; see Art/blender/retail/ref-analysis.md for how +# they were read and why they are assumptions at ±0.03 H. +LD_SHOP_TOP = 0.36 * LD_H # 4.36 m: shopfront glazing / cornice soffit +LD_CORNICE_TOP = 0.43 * LD_H # 5.20 m: top of the bronze band +LD_MURAL_Z = (0.49 * LD_H, 0.61 * LD_H) +LD_LETTER_Z = (0.65 * LD_H, 0.76 * LD_H) +LD_PARAPET_Z = 0.82 * LD_H # 9.92 m: bottom of the stepped parapet + +B6_SHOP_TOP = 0.32 * B6_H # 3.87 m +B6_FASCIA_TOP = 0.38 * B6_H # 4.60 m +B6_WINDOW_Z = (0.47 * B6_H, 0.64 * B6_H) +B6_PARAPET_Z = 0.74 * B6_H # 8.95 m (first pass used 0.62 and left a 4.6 m blank band) + +BAY = 2.20 # mural / bay rhythm on Lee Drug, assumed +PROJECT = 0.55 # bronze cornice projection, assumed +TOWER_R = 2.10 # Lee Drug corner tower radius, assumed + +PARTS: list[dict] = [] + + +def make_material(name, rgb, rough=0.6, metallic=0.0): + """Principled material with an explicit colour; the export preset carries it as a slot.""" + mat = bpy.data.materials.new(name) + mat.use_nodes = True + bsdf = mat.node_tree.nodes.get("Principled BSDF") + bsdf.inputs["Base Color"].default_value = (rgb[0], rgb[1], rgb[2], 1.0) + bsdf.inputs["Roughness"].default_value = rough + bsdf.inputs["Metallic"].default_value = metallic + return mat + + +def clear_scene(): + bpy.ops.object.select_all(action="SELECT") + bpy.ops.object.delete(use_global=False) + for mesh in list(bpy.data.meshes): + if mesh.users == 0: + bpy.data.meshes.remove(mesh) + + +def cube(name, x, y, z, dx, dy, dz, material): + bpy.ops.mesh.primitive_cube_add(size=1.0, location=(x, y, z)) + obj = bpy.context.active_object + obj.name = name + obj.scale = (dx, dy, dz) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + obj.data.materials.append(material) + return obj + + +def cylinder(name, x, y, z, radius, height, material, vertices=28): + bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=height, + location=(x, y, z)) + obj = bpy.context.active_object + obj.name = name + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + obj.data.materials.append(material) + return obj + + +def bay_centres(x0, x1, margin=0.9, bay=BAY): + """Bay centres; the arithmetic lives in `_common` so it is testable without Blender.""" + return _common.bay_centres(x0, x1, margin=margin, bay=bay) + + +def note(obj, evidence): + PARTS.append({ + "object": obj.name, + "asset": "SM_" + obj.name, + "dims_m": [round(d, 3) for d in obj.dimensions], + "location_m": [round(c, 3) for c in obj.location], + "evidence": evidence, + }) + print(f"[retail] {obj.name:26} {obj.dimensions.x:6.2f} x {obj.dimensions.y:5.2f} " + f"x {obj.dimensions.z:5.2f} m ({evidence})") + + +def build_lee_drug(mats): + """6800 Lee Drug: bronze cornice, mural row, lettering band, stepped Deco parapet, + rounded corner tower.""" + stucco, bronze, mural, glass, white = (mats["stucco"], mats["bronze"], mats["mural"], + mats["glass"], mats["white"]) + lettering, reveal = mats["lettering"], mats["reveal"] + parts = [] + cx = (LD_X0 + LD_X1) / 2.0 + width = LD_X1 - LD_X0 + + # Bronze cornice: the strongest horizontal line on the building. + cornice = cube("LD_Cornice", cx, LD_FACE_Y + PROJECT / 2.0, + (LD_SHOP_TOP + LD_CORNICE_TOP) / 2.0, + width + 0.4, PROJECT, LD_CORNICE_TOP - LD_SHOP_TOP, bronze) + parts.append((cornice, "photographs: projecting bronze cornice over the shopfront; " + f"{PROJECT:.2f} m projection assumed")) + + # Shopfront glazing, recessed behind the face, one panel per bay. + for i, bx in enumerate(bay_centres(LD_X0, LD_X1, margin=1.2), start=1): + panel = cube(f"LD_ShopGlass_{i}", bx, LD_FACE_Y + 0.12, LD_SHOP_TOP / 2.0, + BAY - 0.30, 0.10, LD_SHOP_TOP - 0.55, glass) + parts.append((panel, "photographs: full-height shopfront glazing; bay width and " + "the 0.55 m kickplate are assumed (a bus hides this zone)")) + mullion = cube(f"LD_Mullion_{i}", bx - (BAY - 0.30) / 2.0, LD_FACE_Y - 0.02, + LD_SHOP_TOP / 2.0, 0.10, 0.16, LD_SHOP_TOP - 0.55, bronze) + parts.append((mullion, "assumption: slim mullion at each bay boundary")) + + # Celebrity-portrait panels, one per bay, in the band above the cornice. + # + # First pass modelled these as a shallow plate on the wall plane and they vanished + # at review distance: no shadow, no edge. Now each panel is a near-black plate set + # 0.14 m back inside a stucco frame that stands 0.10 m proud, so the sun casts a + # shadow line down one side and the row reads as a row of pictures. + z0, z1 = LD_MURAL_Z + for i, bx in enumerate(bay_centres(LD_X0, LD_X1, margin=1.2), start=1): + frame = cube(f"LD_MuralFrame_{i}", bx, LD_FACE_Y + 0.20, (z0 + z1) / 2.0, + BAY - 0.55, 0.12, (z1 - z0) + 0.30, white) + parts.append((frame, "photographs: stucco frame around each portrait panel; " + "0.30 m border assumed")) + panel = cube(f"LD_Mural_{i}", bx, LD_FACE_Y + 0.12, (z0 + z1) / 2.0, + BAY - 0.85, 0.06, (z1 - z0), mural) + parts.append((panel, "photographs: row of black-framed portrait panels; count and " + "size assumed from the visible row")) + + # Lettering band: the photographs show dark letters painted straight on the stucco, + # so this is a slightly proud plate in a mid-dark tone rather than a black recess. + # The words themselves remain a decal job; the plate only reserves the band. + lz0, lz1 = LD_LETTER_Z + letter = cube("LD_LetterBand", cx, LD_FACE_Y + 0.10, (lz0 + lz1) / 2.0, + width - 3.6, 0.10, lz1 - lz0, lettering) + parts.append((letter, "photographs: large dark lettering across the upper wall; " + "modelled as a painted band, the text itself is a decal job")) + + # Stepped Deco parapet. A single 2.18 m wall with 0.10 m reveals read as one blank + # white band, so it is now two tiers with a 0.10 m step between them, and the + # reveals are 0.22 m tall and cut 0.16 m deep - deep enough to hold a shadow. + tier_h = (LD_H - LD_PARAPET_Z) / 2.0 + lower = cube("LD_ParapetLower", cx, LD_FACE_Y + 0.14, LD_PARAPET_Z + tier_h / 2.0, + width + 0.2, 0.34, tier_h, stucco) + parts.append((lower, "photographs: parapet lower tier above the frontage wall; " + "thickness assumed")) + upper = cube("LD_ParapetUpper", cx, LD_FACE_Y + 0.24, LD_PARAPET_Z + tier_h * 1.5, + width - 3.2, 0.30, tier_h, stucco) + parts.append((upper, "photographs: parapet upper tier, stepped back from the lower " + "one; setback and length assumed")) + for i, (z, span) in enumerate(((LD_PARAPET_Z + tier_h * 0.45, width + 0.2), + (LD_PARAPET_Z + tier_h * 1.30, width + 0.2), + (LD_H - 0.14, width - 3.2)), start=1): + groove = cube(f"LD_ParapetReveal_{i}", cx, LD_FACE_Y - 0.02, z, + span, 0.16, 0.22, reveal) + parts.append((groove, "photographs: horizontal reveal lines in the parapet; " + "count read off the frame")) + + # Rounded corner tower at the east end, stepped twice at the top. The centre + # sits INSIDE the footprint so the tower projects 0.35 m from the face instead of + # bulging 4 m into the pavement, which the first run did. + tower_y = LD_FACE_Y - TOWER_R + 0.35 + tower = cylinder("LD_CornerTower", LD_X1 - TOWER_R, tower_y, + (LD_H - 0.9) / 2.0, TOWER_R, LD_H - 0.9, stucco) + parts.append((tower, "photographs: rounded Deco corner tower rising above the " + f"frontage; {TOWER_R:.2f} m radius assumed")) + # Vertical fins around the curve: the photograph's tower is fluted, not a smooth + # drum. Twelve ribs at this radius are legible from the street. + for i in range(12): + ang = math.pi * (i + 0.5) / 12.0 + fx = (LD_X1 - TOWER_R) + math.cos(ang) * (TOWER_R + 0.06) + fy = tower_y + math.sin(ang) * (TOWER_R + 0.06) + fin = cube(f"LD_TowerFin_{i + 1}", fx, fy, (LD_H - 0.9) / 2.0, + 0.16, 0.16, LD_H - 0.9, white) + parts.append((fin, "photographs: fluted fins on the corner tower; count assumed")) + + # Cap rings stack exactly to the measured 12.10 m top: 11.20 -> 11.75 -> 12.10. + cap_z = LD_H - 0.9 + for i, (dh, radius) in enumerate(((0.55, TOWER_R - 0.35), (0.35, TOWER_R - 0.70)), start=1): + cap = cylinder(f"LD_TowerCap_{i}", LD_X1 - TOWER_R, tower_y, + cap_z + dh / 2.0, radius, dh, white) + parts.append((cap, "photographs: two stepped cap rings on the tower")) + cap_z += dh + return parts + + +def build_6806(mats): + """6806–6820: burgundy fascia, brown pilasters, white-framed windows, banded parapet.""" + stucco, bronze, glass, white, burgundy = (mats["stucco"], mats["bronze"], mats["glass"], + mats["white"], mats["burgundy"]) + reveal = mats["reveal"] + parts = [] + cx = (B6_X0 + B6_X1) / 2.0 + width = B6_X1 - B6_X0 + + fascia = cube("B6_Fascia", cx, B6_FACE_Y - 0.02, + (B6_SHOP_TOP + B6_FASCIA_TOP) / 2.0, width, 0.22, + B6_FASCIA_TOP - B6_SHOP_TOP, burgundy) + parts.append((fascia, "photographs: dark fascia band over the shopfront; colour " + "sampled, height assumed")) + + centres = bay_centres(B6_X0, B6_X1, margin=1.0) + for i, bx in enumerate(centres, start=1): + panel = cube(f"B6_ShopGlass_{i}", bx, B6_FACE_Y + 0.12, B6_SHOP_TOP / 2.0, + BAY - 0.25, 0.10, B6_SHOP_TOP - 0.45, glass) + parts.append((panel, "photographs: framed shopfront glazing behind the fascia")) + # Brown pilaster on every second bay boundary, as the photo shows. + if i % 2 == 1: + pil = cube(f"B6_Pilaster_{i}", bx, B6_FACE_Y - 0.10, + (B6_FASCIA_TOP + B6_PARAPET_Z) / 2.0, 0.40, 0.22, + B6_PARAPET_Z - B6_FASCIA_TOP, bronze) + parts.append((pil, "photographs: brown pilaster strips dividing the frontage")) + + wz0, wz1 = B6_WINDOW_Z + for i, bx in enumerate(centres, start=1): + frame = cube(f"B6_WindowFrame_{i}", bx, B6_FACE_Y - 0.08, (wz0 + wz1) / 2.0, + 1.70, 0.18, wz1 - wz0, white) + parts.append((frame, "photographs: white-framed upper windows with security bars; " + "count and size assumed")) + pane = cube(f"B6_WindowGlass_{i}", bx, B6_FACE_Y + 0.04, (wz0 + wz1) / 2.0, + 1.46, 0.08, wz1 - wz0 - 0.24, glass) + parts.append((pane, "photographs: dark glazing inside each frame")) + # Security bars: three thin verticals per window, the detail that stops the + # upper storey reading as a row of black rectangles. + for b in (-1, 0, 1): + bar = cube(f"B6_WindowBar_{i}_{b + 2}", bx + b * 0.40, B6_FACE_Y - 0.15, + (wz0 + wz1) / 2.0, 0.06, 0.06, wz1 - wz0 - 0.30, white) + parts.append((bar, "photographs: vertical security bars on the upper windows")) + + # Two-tier parapet with deep reveals, as on Lee Drug: one flat band with hairlines + # disappeared at review distance. + tier_h = (B6_H - B6_PARAPET_Z) / 2.0 + para = cube("B6_Parapet", cx, B6_FACE_Y + 0.12, B6_PARAPET_Z + tier_h / 2.0, + width + 0.2, 0.30, tier_h, stucco) + parts.append((para, "photographs: parapet lower tier above the window storey; " + "thickness assumed")) + upper = cube("B6_ParapetUpper", cx, B6_FACE_Y + 0.20, B6_PARAPET_Z + tier_h * 1.5, + width - 2.4, 0.26, tier_h, stucco) + parts.append((upper, "photographs: parapet upper tier stepped back; length assumed")) + for i, (z, span) in enumerate(((B6_PARAPET_Z + tier_h * 0.45, width + 0.2), + (B6_PARAPET_Z + tier_h * 1.30, width + 0.2), + (B6_H - 0.12, width - 2.4)), start=1): + groove = cube(f"B6_ParapetReveal_{i}", cx, B6_FACE_Y - 0.02, z, + span, 0.14, 0.18, reveal) + parts.append((groove, "photographs: reveal lines in the parapet; count assumed")) + return parts + + +def join_parts(parts, name): + """Join one building's parts into a single mesh so Unreal imports one asset. + + The per-part dimensions and evidence are recorded before this runs, so joining + loses nothing from the report. Materials survive as separate slots. + """ + bpy.ops.object.select_all(action="DESELECT") + for obj, _text in parts: + obj.select_set(True) + bpy.context.view_layer.objects.active = parts[0][0] + bpy.ops.object.join() + joined = bpy.context.active_object + joined.name = name + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + return joined + + +def export_objects(objects, out_dir): + count = 0 + for obj in objects: + bpy.ops.object.select_all(action="DESELECT") + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + fbx_preset.export_static(os.path.join(out_dir, "SM_" + obj.name + ".fbx"), + selection=True) + count += 1 + return count + + +def main(argv=None) -> int: + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--out", default="/tmp/gigline/retail") + ap.add_argument("--report", default=None) + args = ap.parse_args(argv) + os.makedirs(args.out, exist_ok=True) + + clear_scene() + mats = { + "stucco": make_material("M_Retail_Stucco", (0.86, 0.83, 0.76), rough=0.72), + "bronze": make_material("M_Retail_Bronze", (0.20, 0.11, 0.06), rough=0.45, metallic=0.35), + "mural": make_material("M_Retail_Mural", (0.035, 0.032, 0.030), rough=0.55), + "glass": make_material("M_Retail_Glass", (0.035, 0.045, 0.055), rough=0.15), + "white": make_material("M_Retail_White", (0.90, 0.88, 0.84), rough=0.60), + "burgundy": make_material("M_Retail_Burgundy", (0.20, 0.09, 0.09), rough=0.55), + # Painted lettering and shadow reveals: same value family as the wall so they + # read as paint and shadow, not as black holes. + "lettering": make_material("M_Retail_Lettering", (0.30, 0.27, 0.24), rough=0.65), + "reveal": make_material("M_Retail_Reveal", (0.42, 0.38, 0.34), rough=0.70), + } + + ld_parts = build_lee_drug(mats) + b6_parts = build_6806(mats) + for obj, text in ld_parts + b6_parts: + note(obj, text) + + # One mesh per building: 85 separate actors over the frontage would cost more + # draw calls than the trim is worth, and the staging step stays two spawns. + joined = [join_parts(ld_parts, "Retail_LeeDrug"), join_parts(b6_parts, "Retail_6806")] + for obj in joined: + print(f"[retail] joined {obj.name}: {len(obj.data.vertices)} verts, " + f"{len(obj.data.materials)} material slot(s), " + f"X {min(v.co.x for v in obj.data.vertices):.2f}.." + f"{max(v.co.x for v in obj.data.vertices):.2f}") + exported = export_objects(joined, args.out) + print(f"[retail] exported {exported} object(s) to {args.out}") + + if args.report: + with open(args.report, "w", encoding="utf-8") as handle: + json.dump({ + "scope": ["6800 Lee Drug (way 424024149)", "6806-6820 (way 424024150)"], + "deferred": ["6822 Budget (way 424024144): no photograph, not modelled"], + "bounds_m": { + "lee_drug": [LD_X0, LD_X1, LD_FACE_Y, LD_H], + "b6806": [B6_X0, B6_X1, B6_FACE_Y, B6_H], + }, + "reference": "Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg " + "(Gb321, CC BY-SA 4.0)", + "analysis": "Art/blender/retail/ref-analysis.md", + "parts": PARTS, + }, handle, indent=2) + print(f"[retail] report -> {args.report}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Tools/tests/test_retail_frontage.py b/Tools/tests/test_retail_frontage.py new file mode 100644 index 0000000..412ec1b --- /dev/null +++ b/Tools/tests/test_retail_frontage.py @@ -0,0 +1,32 @@ +"""Tests for the pure layout helper in Tools/retail_frontage_build.py. + +The builder itself needs Blender; `bay_centres` does not, so the bay rhythm the +façade depends on is checkable without starting Blender or Unreal. +""" + +import unittest + +from Tools._common import bay_centres + + +class BayCentreTests(unittest.TestCase): + def test_centres_stay_inside_the_margins(self): + centres = bay_centres(-33.20, -14.37, margin=1.2) + self.assertGreaterEqual(min(centres), -33.20 + 1.2 - 1e-9) + self.assertLessEqual(max(centres), -14.37 - 1.2 + 1e-9) + + def test_bays_are_evenly_spaced(self): + centres = bay_centres(-66.08, -33.07, margin=1.0) + steps = {round(b - a, 6) for a, b in zip(centres, centres[1:])} + self.assertEqual(len(steps), 1, f"uneven bay spacing: {steps}") + + def test_bay_count_is_the_round_of_span_over_bay(self): + # 33.01 m frontage, 1.0 m margin each end -> 31.01 m -> 14 bays at 2.2 m. + self.assertEqual(len(bay_centres(-66.08, -33.07, margin=1.0)), 14) + + def test_degenerate_frontage_returns_nothing(self): + self.assertEqual(bay_centres(0.0, 1.0, margin=1.0), []) + + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/ue_apply_facade_kit.py b/Tools/ue_apply_facade_kit.py index 42c5091..0cf9ac7 100644 --- a/Tools/ue_apply_facade_kit.py +++ b/Tools/ue_apply_facade_kit.py @@ -235,9 +235,17 @@ def main() -> None: removed += 1 log(f"cleared {removed} previous kit actors") + # The kit is the fallback for buildings that have nothing but a blockout. These + # two are modelled from their own photograph in 0.1.6 + # (Tools/retail_frontage_build.py); pasting the generic kit over them put a + # second, contradicting facade language on the same wall. + MODELLED = ("Frontage_SM_LeeDrug_6800", "Frontage_SM_Hollywood_6806") targets = [a for a in unreal.EditorLevelLibrary.get_all_level_actors() - if a.get_actor_label().startswith(("Frontage_", "North_"))] - log(f"frontage actors: {len(targets)}") + if a.get_actor_label().startswith(("Frontage_", "North_")) + and a.get_actor_label() not in MODELLED] + skipped = [a.get_actor_label() for a in unreal.EditorLevelLibrary.get_all_level_actors() + if a.get_actor_label() in MODELLED] + log(f"frontage actors: {len(targets)} (kit skipped on modelled facades: {skipped})") total = 0 for actor in targets: total += apply_to_actor(actor, kit, materials) diff --git a/Tools/ue_build_demo_map.py b/Tools/ue_build_demo_map.py index 4a57c66..3e017ba 100644 --- a/Tools/ue_build_demo_map.py +++ b/Tools/ue_build_demo_map.py @@ -305,7 +305,8 @@ def build(): log(f"could not create {MAP}") return - removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "ShotSequencer")) + removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "RetailTrim_", + "ShotSequencer")) log(f"cleared {removed} previous demo actors") # --- lighting: fixed daytime ------------------------------------------ @@ -597,6 +598,37 @@ def build(): staged_frontage += 1 log(f"staged {staged_frontage} frontage buildings") + # --- retail frontage trim (0.1.6) ------------------------------------- + # The two retail buildings beside the hero are modelled from their own + # photograph (Art/reference/retail-neighbours/); the trim is laid over the + # blockout volume, so it keeps the trim's own sampled materials rather than a + # look-dev override. 6822 Budget has no photograph and stays a bare volume. + for asset_name, label in (("SM_Retail_LeeDrug", "6800 Lee Drug trim"), + ("SM_Retail_6806", "6806-6820 trim")): + mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Frontage/{asset_name}") + if mesh is None: + log(f"MISSING trim {asset_name} — run Tools/ue_import_retail_detail.py") + continue + # RetailTrim_ rather than Frontage_ so the kit assembler leaves it alone. + actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"RetailTrim_{asset_name}") + if actor: + comp = actor.static_mesh_component + comp.set_static_mesh(mesh) + comp.set_mobility(unreal.ComponentMobility.MOVABLE) + # Map the trim's own slots onto the look-dev materials where the surface + # type matches, so the walls sit in the same noise/texture family as the + # rest of the block. The trim's colour slots (bronze, burgundy, mural, + # lettering) stay as authored: those tones came from the photograph. + TRIM_LOOK = {"M_Retail_Stucco": "M_Stucco", "M_Retail_White": "M_Stucco", + "M_Retail_Glass": "M_FacadeGlass"} + mapped = 0 + for index, slot in enumerate(mesh.get_editor_property("static_materials")): + target = TRIM_LOOK.get(str(slot.get_editor_property("material_slot_name"))) + if target and target in built: + comp.set_material(index, built[target]) + mapped += 1 + log(f"staged: {label} ({mapped} slot(s) onto look materials)") + # --- player start: north sidewalk, facing the hero frontage ------------- # Signs were measured from the imported meshes rather than assumed: the FBX # preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH diff --git a/Tools/ue_build_review_map.py b/Tools/ue_build_review_map.py index 7280632..6be4252 100644 --- a/Tools/ue_build_review_map.py +++ b/Tools/ue_build_review_map.py @@ -300,6 +300,12 @@ try: # on the north sidewalk looking along the crosswalk itself, which is # the pedestrian's view and shows the zebra bars as bars. ("demo-crossing", unreal.Vector(-15250, -2500, 400), unreal.Vector(-14956, 400, 150)), + # 0.1.6 retail frontage: 6800 Lee Drug (X -3340..-1417, face Y +1467) + # and 6806-6820 (X -6618..-3297, face Y +1252). Stand on the north + # sidewalk at 2 m, look south with a level horizon so the parapets and + # the cornice line read as horizontals. 68 deg covers both frontages at + # this distance; the ends crop rather than the elevation leaning. + ("demo-retail-frontage", unreal.Vector(-4700, -2600, 210), unreal.Vector(-4700, 1400, 620), 66.0), # the two north-side background buildings (TCL, Tussaud's) seen from # the south across Hollywood Blvd. Probed world-space (2026-11-08): # TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071.. diff --git a/Tools/ue_import_retail_detail.py b/Tools/ue_import_retail_detail.py new file mode 100644 index 0000000..313646e --- /dev/null +++ b/Tools/ue_import_retail_detail.py @@ -0,0 +1,94 @@ +# Import the retail frontage trim (6800 Lee Drug, 6806–6820) into Unreal. +# +# blender -b --factory-startup --python Tools/retail_frontage_build.py -- \ +# --out /tmp/gigline/retail --report /tmp/gigline/retail-report.json +# /Engine/Binaries/Linux/UnrealEditor-Cmd -run=pythonscript \ +# -script=/Tools/ue_import_retail_detail.py -nullrhi -unattended -nopause -stdout +# +# Two meshes arrive, one per building, each already at its world position in the +# project frame (the FBX carries the offset), so staging spawns at the origin. +# Destination: /Game/Hollywood/Frontage/ — same folder as the block frontage +# so the demo builder stages them together. +# +# Result file: /tmp/gigline/retail-import.txt + +import glob +import os + +import unreal + +SRC = "/tmp/gigline/retail" +DEST = "/Game/Hollywood/Frontage" +RESULT = "/tmp/gigline/retail-import.txt" +lines = [] + + +def log(msg): + lines.append(str(msg)) + unreal.log(str(msg)) + + +def import_fbx(path, name): + task = unreal.AssetImportTask() + task.filename = path + task.destination_path = DEST + task.destination_name = name + task.automated = True + task.save = True + task.replace_existing = True + + ui = unreal.FbxImportUI() + ui.import_mesh = True + ui.import_materials = True + ui.import_textures = False + ui.import_as_skeletal = False + smd = ui.static_mesh_import_data + smd.import_uniform_scale = 1.0 + smd.combine_meshes = True + smd.convert_scene_unit = False + smd.force_front_x_axis = False + smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + + mesh = unreal.EditorAssetLibrary.load_asset(f"{DEST}/{name}") + if mesh is None: + log(f"MISSING after import: {DEST}/{name}") + return None + + # Trim is decoration on a solid the player already collides with: no collision + # of its own, otherwise the cornice and mullions become snag points. + try: + body = mesh.get_editor_property("body_setup") + body.set_editor_property("collision_trace_flag", + unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE) + mesh.set_editor_property("body_setup", body) + except Exception as exc: # noqa: BLE001 + log(f"{name}: collision flag not set ({exc!r})") + + box = mesh.get_bounding_box() + mn, mx = box.min, box.max + log(f"{name:22} X {mn.x:8.0f}..{mx.x:8.0f} Y {mn.y:7.0f}..{mx.y:7.0f} " + f"Z {mn.z:5.0f}..{mx.z:5.0f} slots {len(mesh.get_editor_property('static_materials'))}") + return mesh + + +def main(): + files = sorted(glob.glob(os.path.join(SRC, "*.fbx"))) + if not files: + log(f"no FBX in {SRC}; run Tools/retail_frontage_build.py first") + return + for path in files: + import_fbx(path, os.path.splitext(os.path.basename(path))[0]) + unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True) + + +try: + main() +except Exception as exc: # noqa: BLE001 + import traceback + log(f"FAILED: {type(exc).__name__}: {exc}") + log(traceback.format_exc()) + +with open(RESULT, "w") as f: + f.write("\n".join(lines) + "\n") diff --git a/Tools/visual_targets.json b/Tools/visual_targets.json index 34eed14..88f2fba 100644 --- a/Tools/visual_targets.json +++ b/Tools/visual_targets.json @@ -49,6 +49,18 @@ ], "expect": "The two north-side buildings (TCL Chinese Theatre, Madame Tussaud's) seen from across Hollywood Boulevard, full height in frame, daylight" }, + { + "name": "demo-retail-frontage", + "shot": "Screenshots/demo/review-demo-retail-frontage.png", + "size": [ + 1920, + 1080 + ], + "references": [ + "Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg" + ], + "expect": "The retail run beside the hero from across the street: 6800 Lee Drug's bronze cornice, mural row and stepped parapet next to 6806-6820's pilasters and window storey, daylight" + }, { "name": "demo-third-person", "shot": "Screenshots/demo/review-demo-third-person.png", @@ -62,4 +74,4 @@ "expect": "Third-person view of the player body standing on the north sidewalk, full figure visible from behind, the hero facade across the street, daylight" } ] -} \ No newline at end of file +}