2 Commits
Author SHA1 Message Date
zwitschi 3942e9a215 feat(facades): 0.1.6 retail second pass — relief, material mapping, framing
What the first pass got wrong, and what changed:

- The mural plates were placed 0.20 m INSIDE the solid blockout volume, so they
  never rendered. Nothing can be recessed into a solid we do not cut: the inset is
  now faked by a proud frame with a near-flush plate, and the row reads.
- Detail sat 0.05 m off the wall, under a pixel of depth at 40 m. Trim planes now
  stand 0.10-0.20 m proud, and the parapet became two tiers with deeper reveals.
- 6806's parapet started at 0.62 H and left a 4.6 m blank band; the window storey
  and parapet moved up to follow the photograph.
- Security bars on the upper windows, twelve fluted fins on the corner tower.
- The trim's stucco/white/glass slots are mapped onto the demo's look materials, so
  the walls sit in the same noise/texture family as the rest of the block; the
  colour slots sampled from the photograph stay as authored.
- Review camera pulled back and widened to 66 deg so the east end is in frame.
- The scene audit caught duplicate RetailTrim_ actors: the demo builder's clear list
  did not include the new label, so every rebuild stacked another copy.

Still open and not fixable by geometry: wall lettering. A plate at 0.10 m does not
read at 40 m, so it moves to the 0.9.0 decal route (TODO.md, Screenshots/README.md).
2026-11-09 09:54:33 +01:00
zwitschi 4132f42ca1 plan: give realistic buildings their own minor milestone (0.9.0)
The facade work started as a patch (0.1.4) and the 0.1.6 retail pass showed what
that costs: a patch has no release gate of its own, so a first pass that does not
yet read as the building can sit inside a done patch series. Realism is also the
leading quality goal, which a patch cannot carry.

New 0.9.0 - Realistic buildings, before the feature freeze: facade system with more
than one order, a 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. Former 0.9.0 freeze/beta/audit becomes 0.10.0 with
patches 0.10.1-0.10.3; 1.0.0 moves to weeks 39-44.

Recorded as ADR-8 (append-only) with a measurable gate BR-1..BR-5 in the quality
requirements, mirrored into the combined architecture.md. The 0.1.6 item now says
which of its gaps move to 0.9.0 and why more geometry cannot close them.
2026-11-09 09:54:15 +01:00
42 changed files with 248 additions and 93 deletions

No files matched your search

@@ -55,3 +55,11 @@
- **Decision**: All Blender work driven by Python scripts in `Tools/`. Skill packs used as method guides (checklists, naming, proportions) only. - **Decision**: All Blender work driven by Python scripts in `Tools/`. Skill packs used as method guides (checklists, naming, proportions) only.
- **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI. - **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.
@@ -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 | 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. > 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 |
+18
View File
@@ -455,6 +455,14 @@ Rule: place a building only against a neighbour or street mesh, never in isolati
# Quality Requirements # Quality Requirements
## 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.
## Quality Requirements Overview ## Quality Requirements Overview
Ordered by priority: realism first, performance last. The order decides design trade-offs; it does not license dropping a lower item silently (see the trade-off rule below the table). Ordered by priority: realism first, performance last. The order decides design trade-offs; it does not license dropping a lower item silently (see the trade-off rule below the table).
@@ -489,6 +497,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. > 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 # Risks and Technical Debts
| ID | Risk / Debt | Priority | Mitigation | | ID | Risk / Debt | Priority | Mitigation |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -2
View File
@@ -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.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.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.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 | | `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 |
| `1.0.0` | 35–40 | Stable neighborhood-scale walking/driving release | `1.0.x` maintenance and compatibility 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. Finish each milestone's release checks before expanding scope. Cut map size and simulation complexity before compromising the core walking/driving experience.
+22 -6
View File
@@ -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. **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. **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. **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`. **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. **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.
+2 -2
View File
@@ -85,8 +85,8 @@ Read this one against the reference photograph it was modelled from: `Art/refere
Known gaps in this first pass, recorded rather than hidden: Known gaps in this first pass, recorded rather than hidden:
- **The parapet reads as a blank white band.** It is modelled as one 2.18 m wall with four 0.10 m reveal strips, and at review distance the strips disappear into the wash. - **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 and lettering band barely read.** They are shallow plates (0.06–0.08 m) on the same plane as the wall; they need either depth or a decal before they read as the photographs' black-framed panels. - **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 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. - **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. - **6822 Budget is untouched** and still a bare blockout volume: no photograph of it exists.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+51 -10
View File
@@ -15,7 +15,7 @@ Suggested blender skills: **references** (reference-image-match), **archviz**, *
### Patch tasks - active - session-5598ce7e-27e3-4c64-bb49-c04a4a31ee58 (branch `facade-retail-0.1.6`) ### Patch tasks - active - session-5598ce7e-27e3-4c64-bb49-c04a4a31ee58 (branch `facade-retail-0.1.6`)
- [A] **Model 6800 Lee Drug and 6806–6820 from their own reference.** First pass 2026-11-09 on branch `facade-retail-0.1.6`: `Tools/retail_frontage_build.py` builds both façades from the one photograph (`Art/reference/retail-neighbours/`, Gb321 CC BY-SA 4.0) with the analysis in `Art/blender/retail/ref-analysis.md`; `Tools/ue_import_retail_detail.py` imports them and the demo builder stages them as `RetailTrim_*`. The generic façade kit is now skipped on these two, so their walls carry one façade language instead of two. **Open on this item:** the parapet reads as a blank band, the mural row and lettering band do not resolve at review distance, the corner tower is a plain cylinder, and the review camera crops the east end — all listed in `Screenshots/README.md`. - [A] **Model 6800 Lee Drug and 6806–6820 from their own reference.** First pass 2026-11-09 on branch `facade-retail-0.1.6`: `Tools/retail_frontage_build.py` builds both façades from the one photograph (`Art/reference/retail-neighbours/`, Gb321 CC BY-SA 4.0) with the analysis in `Art/blender/retail/ref-analysis.md`; `Tools/ue_import_retail_detail.py` imports them and the demo builder stages them as `RetailTrim_*`. The generic façade kit is now skipped on these two, so their walls carry one façade language instead of two. **Open on this item:** the parapet reads as a blank band, the mural row and lettering band do not resolve at review distance, the corner tower is a plain cylinder, and the review camera crops the east end — all listed in `Screenshots/README.md`. **Not fixable by more geometry:** a second pass moved the plates 0.10–0.20 m off the wall and they still do not read at 40 m; wall lettering and the portrait panels need the decal/texture route scoped in `0.9.0 — Realistic buildings`. This patch stops at the trim that does read — cornice, fascia, storefronts, window bars, parapet tiers.
- [D] **Model 6822 Budget.** Deferred: no photograph exists for it (Commons has nothing usable) and the single-storey 6.0 × 5.5 m volume cannot be read from the neighbour shot. Needs photography before it can be modelled honestly; do not substitute the El Capitan Theatre's set or the 6806–6820 shot. - [D] **Model 6822 Budget.** Deferred: no photograph exists for it (Commons has nothing usable) and the single-storey 6.0 × 5.5 m volume cannot be read from the neighbour shot. Needs photography before it can be modelled honestly; do not substitute the El Capitan Theatre's set or the 6806–6820 shot.
- [ ] Review every façade change with `Tools/render_screenshots.sh --verify` before tagging. - [ ] 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. - [ ] Tag `v0.1.6` and write `CHANGELOG.md` entry; then remove this section.
@@ -286,7 +286,48 @@ Suggested blender skills: **references** (reference-image-match), **archviz**, *
- [ ] Fix compatibility issues from the hardware testing matrix. - [ ] Fix compatibility issues from the hardware testing matrix.
- [ ] Tag `v0.8.3` and write `CHANGELOG.md` entry; then remove this section. - [ ] 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.
- [ ] 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 — parapet profile, mural panels, and the lettering decal.
- [ ] 6822 Budget, 6806–6820 and 6800 photography gap: acquire references before modelling anything not yet photographed, and record the sources checked in `Geo/`.
- [ ] 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**: 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 ### Freeze and audit
@@ -298,29 +339,29 @@ Suggested blender skills: **references** (reference-image-match), **archviz**, *
### Release checks ### Release checks
- [ ] Beta feedback triaged; blockers logged for `0.9.x`. - [ ] Beta feedback triaged; blockers logged for `0.10.x`.
- [ ] Tag `v0.9.0` and write `CHANGELOG.md` entry; then remove this section. - [ ] 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 ### Patch tasks
- [ ] Fix blockers reported during the public beta. - [ ] 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 ### Patch tasks
- [ ] Fix regressions introduced by `0.9.x` fixes. - [ ] 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 ### Patch tasks
- [ ] Update all `Docs/` for accuracy before `1.0.0`. - [ ] 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) ## 1.0.0 — Stable neighborhood-scale walking/driving release (weeks 35–40)
+78 -30
View File
@@ -58,8 +58,8 @@ 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_SHOP_TOP = 0.32 * B6_H # 3.87 m
B6_FASCIA_TOP = 0.38 * B6_H # 4.60 m B6_FASCIA_TOP = 0.38 * B6_H # 4.60 m
B6_WINDOW_Z = (0.45 * B6_H, 0.60 * B6_H) B6_WINDOW_Z = (0.47 * B6_H, 0.64 * B6_H)
B6_PARAPET_Z = 0.62 * B6_H # 7.50 m 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 BAY = 2.20 # mural / bay rhythm on Lee Drug, assumed
PROJECT = 0.55 # bronze cornice projection, assumed PROJECT = 0.55 # bronze cornice projection, assumed
@@ -129,6 +129,7 @@ def build_lee_drug(mats):
rounded corner tower.""" rounded corner tower."""
stucco, bronze, mural, glass, white = (mats["stucco"], mats["bronze"], mats["mural"], stucco, bronze, mural, glass, white = (mats["stucco"], mats["bronze"], mats["mural"],
mats["glass"], mats["white"]) mats["glass"], mats["white"])
lettering, reveal = mats["lettering"], mats["reveal"]
parts = [] parts = []
cx = (LD_X0 + LD_X1) / 2.0 cx = (LD_X0 + LD_X1) / 2.0
width = LD_X1 - LD_X0 width = LD_X1 - LD_X0
@@ -151,31 +152,49 @@ def build_lee_drug(mats):
parts.append((mullion, "assumption: slim mullion at each bay boundary")) parts.append((mullion, "assumption: slim mullion at each bay boundary"))
# Celebrity-portrait panels, one per bay, in the band above the cornice. # 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 z0, z1 = LD_MURAL_Z
for i, bx in enumerate(bay_centres(LD_X0, LD_X1, margin=1.2), start=1): for i, bx in enumerate(bay_centres(LD_X0, LD_X1, margin=1.2), start=1):
panel = cube(f"LD_Mural_{i}", bx, LD_FACE_Y - 0.04, (z0 + z1) / 2.0, frame = cube(f"LD_MuralFrame_{i}", bx, LD_FACE_Y + 0.20, (z0 + z1) / 2.0,
BAY - 0.75, 0.08, z1 - z0, mural) 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 " parts.append((panel, "photographs: row of black-framed portrait panels; count and "
"size assumed from the visible row")) "size assumed from the visible row"))
# Lettering band: a recessed plate, not text. Reproducing the lettering needs a # Lettering band: the photographs show dark letters painted straight on the stucco,
# decal, which is a materials-pass job. # 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 lz0, lz1 = LD_LETTER_Z
letter = cube("LD_LetterBand", cx, LD_FACE_Y - 0.03, (lz0 + lz1) / 2.0, letter = cube("LD_LetterBand", cx, LD_FACE_Y + 0.10, (lz0 + lz1) / 2.0,
width - 3.0, 0.06, lz1 - lz0, mural) width - 3.6, 0.10, lz1 - lz0, lettering)
parts.append((letter, "photographs: large dark lettering across the upper wall; " parts.append((letter, "photographs: large dark lettering across the upper wall; "
"modelled as a recessed plate, the text itself is a decal job")) "modelled as a painted band, the text itself is a decal job"))
# Stepped Deco parapet: wall band plus four horizontal reveals. # Stepped Deco parapet. A single 2.18 m wall with 0.10 m reveals read as one blank
para = cube("LD_Parapet", cx, LD_FACE_Y + 0.10, (LD_PARAPET_Z + LD_H) / 2.0, # white band, so it is now two tiers with a 0.10 m step between them, and the
width + 0.2, 0.30, LD_H - LD_PARAPET_Z, stucco) # reveals are 0.22 m tall and cut 0.16 m deep - deep enough to hold a shadow.
parts.append((para, "photographs: parapet steps above the frontage wall; " tier_h = (LD_H - LD_PARAPET_Z) / 2.0
"thickness assumed")) lower = cube("LD_ParapetLower", cx, LD_FACE_Y + 0.14, LD_PARAPET_Z + tier_h / 2.0,
for i in range(4): width + 0.2, 0.34, tier_h, stucco)
z = LD_PARAPET_Z + (LD_H - LD_PARAPET_Z) * (i + 1) / 5.0 parts.append((lower, "photographs: parapet lower tier above the frontage wall; "
groove = cube(f"LD_ParapetReveal_{i + 1}", cx, LD_FACE_Y - 0.03, z, "thickness assumed"))
width + 0.2, 0.07, 0.10, mural) upper = cube("LD_ParapetUpper", cx, LD_FACE_Y + 0.24, LD_PARAPET_Z + tier_h * 1.5,
parts.append((groove, "photographs: four horizontal reveal lines in the parapet; " 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")) "count read off the frame"))
# Rounded corner tower at the east end, stepped twice at the top. The centre # Rounded corner tower at the east end, stepped twice at the top. The centre
@@ -186,6 +205,16 @@ def build_lee_drug(mats):
(LD_H - 0.9) / 2.0, TOWER_R, LD_H - 0.9, stucco) (LD_H - 0.9) / 2.0, TOWER_R, LD_H - 0.9, stucco)
parts.append((tower, "photographs: rounded Deco corner tower rising above the " parts.append((tower, "photographs: rounded Deco corner tower rising above the "
f"frontage; {TOWER_R:.2f} m radius assumed")) 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 rings stack exactly to the measured 12.10 m top: 11.20 -> 11.75 -> 12.10.
cap_z = LD_H - 0.9 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): for i, (dh, radius) in enumerate(((0.55, TOWER_R - 0.35), (0.35, TOWER_R - 0.70)), start=1):
@@ -200,6 +229,7 @@ def build_6806(mats):
"""6806–6820: burgundy fascia, brown pilasters, white-framed windows, banded parapet.""" """6806–6820: burgundy fascia, brown pilasters, white-framed windows, banded parapet."""
stucco, bronze, glass, white, burgundy = (mats["stucco"], mats["bronze"], mats["glass"], stucco, bronze, glass, white, burgundy = (mats["stucco"], mats["bronze"], mats["glass"],
mats["white"], mats["burgundy"]) mats["white"], mats["burgundy"])
reveal = mats["reveal"]
parts = [] parts = []
cx = (B6_X0 + B6_X1) / 2.0 cx = (B6_X0 + B6_X1) / 2.0
width = B6_X1 - B6_X0 width = B6_X1 - B6_X0
@@ -224,22 +254,36 @@ def build_6806(mats):
wz0, wz1 = B6_WINDOW_Z wz0, wz1 = B6_WINDOW_Z
for i, bx in enumerate(centres, start=1): for i, bx in enumerate(centres, start=1):
frame = cube(f"B6_WindowFrame_{i}", bx, B6_FACE_Y - 0.06, (wz0 + wz1) / 2.0, frame = cube(f"B6_WindowFrame_{i}", bx, B6_FACE_Y - 0.08, (wz0 + wz1) / 2.0,
1.70, 0.14, wz1 - wz0, white) 1.70, 0.18, wz1 - wz0, white)
parts.append((frame, "photographs: white-framed upper windows with security bars; " parts.append((frame, "photographs: white-framed upper windows with security bars; "
"count and size assumed")) "count and size assumed"))
pane = cube(f"B6_WindowGlass_{i}", bx, B6_FACE_Y + 0.02, (wz0 + wz1) / 2.0, 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) 1.46, 0.08, wz1 - wz0 - 0.24, glass)
parts.append((pane, "photographs: dark glazing inside each frame")) 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"))
para = cube("B6_Parapet", cx, B6_FACE_Y + 0.08, (B6_PARAPET_Z + B6_H) / 2.0, # Two-tier parapet with deep reveals, as on Lee Drug: one flat band with hairlines
width + 0.2, 0.26, B6_H - B6_PARAPET_Z, stucco) # disappeared at review distance.
parts.append((para, "photographs: parapet band above the window storey; thickness assumed")) tier_h = (B6_H - B6_PARAPET_Z) / 2.0
for i in range(3): para = cube("B6_Parapet", cx, B6_FACE_Y + 0.12, B6_PARAPET_Z + tier_h / 2.0,
z = B6_PARAPET_Z + (B6_H - B6_PARAPET_Z) * (i + 1) / 4.0 width + 0.2, 0.30, tier_h, stucco)
groove = cube(f"B6_ParapetReveal_{i + 1}", cx, B6_FACE_Y - 0.03, z, parts.append((para, "photographs: parapet lower tier above the window storey; "
width + 0.2, 0.07, 0.09, bronze) "thickness assumed"))
parts.append((groove, "photographs: three reveal lines in the parapet; count 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 return parts
@@ -289,6 +333,10 @@ def main(argv=None) -> int:
"glass": make_material("M_Retail_Glass", (0.035, 0.045, 0.055), rough=0.15), "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), "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), "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) ld_parts = build_lee_drug(mats)
+15 -2
View File
@@ -305,7 +305,8 @@ def build():
log(f"could not create {MAP}") log(f"could not create {MAP}")
return 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") log(f"cleared {removed} previous demo actors")
# --- lighting: fixed daytime ------------------------------------------ # --- lighting: fixed daytime ------------------------------------------
@@ -614,7 +615,19 @@ def build():
comp = actor.static_mesh_component comp = actor.static_mesh_component
comp.set_static_mesh(mesh) comp.set_static_mesh(mesh)
comp.set_mobility(unreal.ComponentMobility.MOVABLE) comp.set_mobility(unreal.ComponentMobility.MOVABLE)
log(f"staged: {label}") # 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 ------------- # --- player start: north sidewalk, facing the hero frontage -------------
# Signs were measured from the imported meshes rather than assumed: the FBX # Signs were measured from the imported meshes rather than assumed: the FBX
+1 -1
View File
@@ -305,7 +305,7 @@ try:
# sidewalk at 2 m, look south with a level horizon so the parapets and # 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 # the cornice line read as horizontals. 68 deg covers both frontages at
# this distance; the ends crop rather than the elevation leaning. # this distance; the ends crop rather than the elevation leaning.
("demo-retail-frontage", unreal.Vector(-4700, -2000, 200), unreal.Vector(-4700, 1400, 560), 68.0), ("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 two north-side background buildings (TCL, Tussaud's) seen from
# the south across Hollywood Blvd. Probed world-space (2026-11-08): # the south across Hollywood Blvd. Probed world-space (2026-11-08):
# TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071.. # TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..