feat(facades): 0.1.4 — module kit, textured materials, theatre trim (#30)

Merged PR #30: 0.1.4 — facade module kit, textured materials, theatre trim

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
This commit was merged in pull request #30.
This commit is contained in:
zwitschi authored and zwitschi committed 2026-11-08 20:29:11 +01:00
1 parent d6d3d54810
commit 5e2c5e29c0
90 files changed
+2592 -413

No files matched your search

+126 -231
View File
@@ -239,289 +239,184 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
## 2026-11-08 — reference photography gaps closed (local-only)
- Closed the two missing reference angles for the hero façade, both from Wikimedia
Commons (gitignored deliverables under `Art/reference/`, provenance in sidecars):
- `el-capitan-entrance/` — `File:Hollywood El Capitan entrance.jpg` (+ detail),
Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door
recess that the notes doc had claimed Commons didn't hold. Query that found it:
`--query "El Capitan entrance"`.
- `el-capitan-night/` — `File:El Capitan theater with Tron marquee.jpg`
(CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is
the building NEXT to 6840; treat as proximity evidence only, not the hero.
- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with
`--delay`/separate calls and wait ~15-20s between runs (`queue.py` tool default
1 req/s is insufficient once rate limited).
- Task was claimed on branch `ref-photo-0.1.2`; delivers only the TODO closure (all
actual reference files are gitignored, so no code branch content). Reported to
coordinator for merge/cleanup.
- Closed the two missing reference angles for the hero façade, both from Wikimedia Commons (gitignored deliverables under `Art/reference/`, provenance in sidecars):
- `el-capitan-entrance/` — `File:Hollywood El Capitan entrance.jpg` (+ detail), Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door recess that the notes doc had claimed Commons didn't hold. Query that found it: `--query "El Capitan entrance"`.
- `el-capitan-night/` — `File:El Capitan theater with Tron marquee.jpg` (CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is the building NEXT to 6840; treat as proximity evidence only, not the hero.
- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with `--delay`/separate calls and wait ~15-20s between runs (`queue.py` tool default 1 req/s is insufficient once rate limited).
- Task was claimed on branch `ref-photo-0.1.2`; delivers only the TODO closure (all actual reference files are gitignored, so no code branch content). Reported to coordinator for merge/cleanup.
## 2026-11-08 — security-game scan: concepts copied, licensing ignored (owner)
- Scanned `/run/media/system/DATA/Documents/06-games/security-game` for reference
material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/
hollywood/`, 9 files) is already fully duplicated in our `Art/reference/hollywood/`
(with SIDECAR/BOARD copies) — nothing new there.
- What we lacked: its **AI concept renders** (`docs/screens/concepts/
hollywood_boulevard/`, 12 PNGs + `.refgen.json`). Copied into
`Art/reference/el-capitan-concepts/` with a folder README. **Owner explicitly
said ignore licensing completely** — they are gitignored local development
reference, never committed or shipped; `.refgen.json` still records the model
and source photo per file for traceability.
- Use concepts for mood/lighting intent only; geometry truth stays with the photo
refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.).
- The security-game `realism/` A/B screenshots and `concepts/joke_shop/` are its
own engine/venue renders — not relevant to our 6840 modeling.
- Scanned `/run/media/system/DATA/Documents/06-games/security-game` for reference material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/ hollywood/`, 9 files) is already fully duplicated in our `Art/reference/hollywood/` (with SIDECAR/BOARD copies) — nothing new there.
- What we lacked: its **AI concept renders** (`docs/screens/concepts/ hollywood_boulevard/`, 12 PNGs + `.refgen.json`). Copied into `Art/reference/el-capitan-concepts/` with a folder README. **Owner explicitly said ignore licensing completely** — they are gitignored local development reference, never committed or shipped; `.refgen.json` still records the model and source photo per file for traceability.
- Use concepts for mood/lighting intent only; geometry truth stays with the photo refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.).
- The security-game `realism/` A/B screenshots and `concepts/joke_shop/` are its own engine/venue renders — not relevant to our 6840 modeling.
## 2026-11-08 — ODbL review closed; remaining active TODO items finished
- Wrote `Docs/odbl-review.md` (decision 2026-11-08): the shipped block geometry
(streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database
(ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("©
OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"),
ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox,
SHA-256). Share-alike is limited to the data layer; project code/art remain CC0
(Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated
to ODbL.
- Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision
verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1
patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height);
(2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays
deferred (tracked separately).
- Wrote `Docs/odbl-review.md` (decision 2026-11-08): the shipped block geometry (streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database (ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("© OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"), ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox, SHA-256). Share-alike is limited to the data layer; project code/art remain CC0 (Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated to ODbL.
- Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1 patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height); (2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays deferred (tracked separately).
- Branch: odbl-review-0.1.0 (docs + TODO closure only).
## 2026-11-08 — placement release check; north-buildings camera was 9.5 km off
- Release check 2 (placement vs references): rendered demo, probed DemoMap world
positions, compared to Geo references. Matches: road 24.3 m (ref crossings
20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero
frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings
+ TCL/Tussaud's in verified OSM positions.
- **Bug found:** the `demo-north-buildings` camera sat at X -22800 (the buildings'
own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry:
TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799,
Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at
(X -22750, Y +1600) looking north; sky fraction 85% -> 41%.
- Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the
world footprint (FBX carries the geo offset). Probe by loading the level
explicitly (load_level) — a fresh commandlet starts on Untitled otherwise.
- Release check 2 (placement vs references): rendered demo, probed DemoMap world positions, compared to Geo references. Matches: road 24.3 m (ref crossings 20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings
- TCL/Tussaud's in verified OSM positions.
- **Bug found:** the `demo-north-buildings` camera sat at X -22800 (the buildings' own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry: TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799, Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at (X -22750, Y +1600) looking north; sky fraction 85% -> 41%.
- Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the world footprint (FBX carries the geo offset). Probe by loading the level explicitly (load_level) — a fresh commandlet starts on Untitled otherwise.
## 2026-11-08 — collision/seam audit done; QA self-test extended
- `-qaselftest` transect widened from 84 samples (3 lane centres) to **1456
samples / 16 rows** including kerb/road/sidewalk seam Ys (probed: N side
-1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side
+718..+1447). 0 holes.
- **New wall probe:** ground-referenced horizontal 30 m trace at +160 cm. First
attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366)
-> every row reported a dist-0 blocker. Always ground the probe first.
- **Guard:** GigLineShotActor must skip rounds under `-qaselftest`, else
bQuitWhenDone closes the game before the transect phase (observed: game exited
at t=2s with "ShotActor: done (4 shots)" + RequestExit).
- Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect
0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md.
- `-qaselftest` transect widened from 84 samples (3 lane centres) to **1456 samples / 16 rows** including kerb/road/sidewalk seam Ys (probed: N side -1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side +718..+1447). 0 holes.
- **New wall probe:** ground-referenced horizontal 30 m trace at +160 cm. First attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366) -> every row reported a dist-0 blocker. Always ground the probe first.
- **Guard:** GigLineShotActor must skip rounds under `-qaselftest`, else bQuitWhenDone closes the game before the transect phase (observed: game exited at t=2s with "ShotActor: done (4 shots)" + RequestExit).
- Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect 0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md.
## 2026-11-08 — setup logs gated behind a cvar (diagnostics item)
- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine
IN:" pushed/bound/errors) are now gated behind `gigline.SetupLogs`
(TAutoConsoleVariable, default **0** via `DefaultEngine.ini [ConsoleVariables]`).
Re-enable at runtime: `gigline.SetupLogs 1`.
- The `-qaselftest` QA logs stay ungated (deliberate release-check feature per
Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite
still all PASS (walk/reset/pause/transect 0 holes/wall 0-35).
- Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the
DefaultKeyMappings fix (PR #8).
- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine IN:" pushed/bound/errors) are now gated behind `gigline.SetupLogs` (TAutoConsoleVariable, default **0** via `DefaultEngine.ini [ConsoleVariables]`). Re-enable at runtime: `gigline.SetupLogs 1`.
- The `-qaselftest` QA logs stay ungated (deliberate release-check feature per Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite still all PASS (walk/reset/pause/transect 0 holes/wall 0-35).
- Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the DefaultKeyMappings fix (PR #8).
## 2026-11-08 — v0.1.0 released
- All six `0.1.0` release checks pass and are recorded in `Docs/release-checks.md`:
traversal (seam transect 0 holes), placement vs references, collision+packaging
(wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG),
fresh-checkout build, packaged Linux build with controls shown in-game.
- `CHANGELOG.md` gets the `0.1.0` entry; the completed `0.1.0` section was removed
from `TODO.md` (goal stays in `ROADMAP.md`); tag `v0.1.0` cut on `main`.
- All six `0.1.0` release checks pass and are recorded in `Docs/release-checks.md`: traversal (seam transect 0 holes), placement vs references, collision+packaging (wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG), fresh-checkout build, packaged Linux build with controls shown in-game.
- `CHANGELOG.md` gets the `0.1.0` entry; the completed `0.1.0` section was removed from `TODO.md` (goal stays in `ROADMAP.md`); tag `v0.1.0` cut on `main`.
- Owner approved the regenerated screenshot set (PR #15) on 2026-11-08.
- THIRD_PARTY_NOTICES switched to checkbox statuses (`[x]`/`[P]`/`[ ]`); all tools
incl. Blosm, QGIS and Git LFS are now verified as tool-only.
- Next work: the `0.1.1` collision-detail patch (sidewalk outer edge 15.8 cm step,
crossing ramp gradient 10 %, kerb height assumption, traversal regression).
- THIRD_PARTY_NOTICES switched to checkbox statuses (`[x]`/`[P]`/`[ ]`); all tools incl. Blosm, QGIS and Git LFS are now verified as tool-only.
- Next work: the `0.1.1` collision-detail patch (sidewalk outer edge 15.8 cm step, crossing ramp gradient 10 %, kerb height assumption, traversal regression).
## 2026-11-08 — v0.1.1 released (collision patch)
- Sidewalk upstand now tapers full-at-kerb → zero at the building edge (removes the
15.8 cm outer ledge). Crossing ramp run derives from `--max-ramp-grade` (8 % ⇒
1.63 m) instead of a fixed 1.5 m/8.7 %. Street + site rebuilt, site re-imported,
DemoMap rebuilt, packaged build verified.
- Pre-check tool fixes: coverage no longer shrinks with `--sample` (it used
`range(int(block_len))`), and it reports grade + location of the worst step.
- Residual, documented not hidden: 13.4 cm surface step at s≈198 (south) / 8.0 cm
at s≈251 (north) — a strip discontinuity near a centreline vertex, below the
45 cm MaxStepHeight, so not blocking.
- Kerb height finalised at 0.15 m modelled / 0.13 m built (6 in LA standard; DEM
and photos can't resolve finer).
- Sidewalk upstand now tapers full-at-kerb → zero at the building edge (removes the 15.8 cm outer ledge). Crossing ramp run derives from `--max-ramp-grade` (8 % ⇒ 1.63 m) instead of a fixed 1.5 m/8.7 %. Street + site rebuilt, site re-imported, DemoMap rebuilt, packaged build verified.
- Pre-check tool fixes: coverage no longer shrinks with `--sample` (it used `range(int(block_len))`), and it reports grade + location of the worst step.
- Residual, documented not hidden: 13.4 cm surface step at s≈198 (south) / 8.0 cm at s≈251 (north) — a strip discontinuity near a centreline vertex, below the 45 cm MaxStepHeight, so not blocking.
- Kerb height finalised at 0.15 m modelled / 0.13 m built (6 in LA standard; DEM and photos can't resolve finer).
## 2026-11-09 — Mapbox building data as a second height source
- `Geo/tools/mapbox_buildings.py` (new, stdlib `urllib`) Tilequeries
`mapbox.mapbox-streets-v8` layer `building` at every building centroid in the
extract and merges the answer with our OSM height + source. Token comes from
`MAPBOX_ACCESS_TOKEN` in the ignored `.env`; responses cache to
`Geo/raw/mapbox-cache/`.
- **Mapbox terms restrict persisting/redistributing their data**, so `.gitignore`
now covers `/Geo/raw/mapbox-cache/` and `/Geo/verification/*mapbox*`. The tool
also refuses to cache a response that contains the token.
- `Geo/tools/mapbox_buildings.py` (new, stdlib `urllib`) Tilequeries `mapbox.mapbox-streets-v8` layer `building` at every building centroid in the extract and merges the answer with our OSM height + source. Token comes from `MAPBOX_ACCESS_TOKEN` in the ignored `.env`; responses cache to `Geo/raw/mapbox-cache/`.
- **Mapbox terms restrict persisting/redistributing their data**, so `.gitignore` now covers `/Geo/raw/mapbox-cache/` and `/Geo/verification/*mapbox*`. The tool also refuses to cache a response that contains the token.
- **First run (72 buildings): 58 match, 5 close, 3 disagree, 6 non-building.**
- It independently classifies our roof/canopy/shelter volumes as `roof`/
`shelter` — corroborating the `0.1.2` finding that they are not buildings.
- Remaining disagreements are untyped `building=yes`/`apartments` rear
structures where our fallback guess (2 × 3.2 m = 6.4 m) meets Mapbox's 3.0 m.
- Every "close" case is a `building:levels × 3.2 m` building where Mapbox is
**0.6 m lower per 6 levels** → Mapbox implies **3.1 m per level**, not 3.2.
Worth revisiting `LEVEL_HEIGHT` if a measured storey height ever lands.
- It independently classifies our roof/canopy/shelter volumes as `roof`/ `shelter` — corroborating the `0.1.2` finding that they are not buildings.
- Remaining disagreements are untyped `building=yes`/`apartments` rear structures where our fallback guess (2 × 3.2 m = 6.4 m) meets Mapbox's 3.0 m.
- Every "close" case is a `building:levels × 3.2 m` building where Mapbox is **0.6 m lower per 6 levels** → Mapbox implies **3.1 m per level**, not 3.2. Worth revisiting `LEVEL_HEIGHT` if a measured storey height ever lands.
- Its query API surface: `height`, `min_height`, `type`, `extrude` per polygon.
## 2026-11-09 — Mapbox calibration adopted into the blockout
- Owner decision: adopt Mapbox. Applied:
- `LEVEL_HEIGHT` 3.2 → **3.1 m** in `Geo/tools/block_height_report.py`, and the
Blosm `--level-height` default in `Art/blender/blockout/blosm_blockout.py`.
- `HEIGHT_OVERRIDES` in block_height_report: ways 1490910971, 1492058875,
1492747608 → 3.0 m (Mapbox single-storey; the 2-level guess gave 6.4 m).
- `classify_height(tags, level_height, way_id)` now takes the way id so
overrides apply; `mapbox_buildings.py` passes it, so the cross-check agrees
with the height table.
- `LEVEL_HEIGHT` 3.2 → **3.1 m** in `Geo/tools/block_height_report.py`, and the Blosm `--level-height` default in `Art/blender/blockout/blosm_blockout.py`.
- `HEIGHT_OVERRIDES` in block_height_report: ways 1490910971, 1492058875, 1492747608 → 3.0 m (Mapbox single-storey; the 2-level guess gave 6.4 m).
- `classify_height(tags, level_height, way_id)` now takes the way id so overrides apply; `mapbox_buildings.py` passes it, so the cross-check agrees with the height table.
- Re-running the cross-check: **66 match, 6 non-building, 0 close/disagree**.
- Geometry: blockout regenerated at 3.1 (object names preserved — the frontage
exporter maps `element.020`/`element`/`element.019`/named buildings, all still
present, 6/6 exported), frontage re-imported, DemoMap rebuilt, packaged build
`-qaselftest` all PASS.
- **Blosm adds roof mass** on top of `levels × levelHeight`: 6922 39.1 m vs table
37.2, El Capitan Theatre 20.5 m vs 18.6 — both ~+1.9 m. That is a roof, not a
height error; the table is the tagged height, the mesh is storey + roof.
- Gotcha: regenerating the blockout is safe for names, but any future change to
the import must re-verify the 6/6 frontage export and the `element.*` mapping.
- Geometry: blockout regenerated at 3.1 (object names preserved — the frontage exporter maps `element.020`/`element`/`element.019`/named buildings, all still present, 6/6 exported), frontage re-imported, DemoMap rebuilt, packaged build `-qaselftest` all PASS.
- **Blosm adds roof mass** on top of `levels × levelHeight`: 6922 39.1 m vs table 37.2, El Capitan Theatre 20.5 m vs 18.6 — both ~+1.9 m. That is a roof, not a height error; the table is the tagged height, the mesh is storey + roof.
- Gotcha: regenerating the blockout is safe for names, but any future change to the import must re-verify the 6/6 frontage export and the `element.*` mapping.
## 2026-11-09 — hero colonnade proportion fixed (47.6 % → 53.1 %)
- The hero's colonnade (columns 2.00–7.95 + capitals 7.95–8.90) was 6.90 m of the
14.50 m elevation = **47.6 %**, below the photographed 50–60 %, because the
entablature (3.40 m) and parapet (2.20 m) used 5.60 m of the height.
- Thinned ENTABLATURE_H 3.40 → 3.00 and PARAPET_H 2.20 → 1.80 in
`Tools/hero_facade_build.py`; the colonnade grows to **7.70 m = 53.1 %** while the
envelope, motto band and medallions keep their relationships (all positioned
relative to those two constants).
- **Gotcha (cost a bad build):** `hero_facade_build.py` takes `--out` for the
**FBX** and `--save-blend` for the `.blend`. Passing `--out <file>.blend` writes
FBX bytes over the blend and Blender then reports "File format is not supported".
Correct invocation: `--out /tmp/gigline/hero-facade.fbx --save-blend Art/...blend
--report Art/...json`.
- Rebuilt hero → re-imported (`SM_HeroFacade`, bounds 2604 × 1257 × 1450 cm) →
DemoMap rebuilt → screenshots re-rendered. Note `ue_hero_facade_import.py` saves
the map, so `chmod u+w` the `.umap` first or the import silently does not persist.
- The hero's colonnade (columns 2.00–7.95 + capitals 7.95–8.90) was 6.90 m of the 14.50 m elevation = **47.6 %**, below the photographed 50–60 %, because the entablature (3.40 m) and parapet (2.20 m) used 5.60 m of the height.
- Thinned ENTABLATURE_H 3.40 → 3.00 and PARAPET_H 2.20 → 1.80 in `Tools/hero_facade_build.py`; the colonnade grows to **7.70 m = 53.1 %** while the envelope, motto band and medallions keep their relationships (all positioned relative to those two constants).
- **Gotcha (cost a bad build):** `hero_facade_build.py` takes `--out` for the **FBX** and `--save-blend` for the `.blend`. Passing `--out <file>.blend` writes FBX bytes over the blend and Blender then reports "File format is not supported". Correct invocation: `--out /tmp/gigline/hero-facade.fbx --save-blend Art/...blend --report Art/...json`.
- Rebuilt hero → re-imported (`SM_HeroFacade`, bounds 2604 × 1257 × 1450 cm) → DemoMap rebuilt → screenshots re-rendered. Note `ue_hero_facade_import.py` saves the map, so `chmod u+w` the `.umap` first or the import silently does not persist.
## 2026-11-09 — AI detail plates from reference photos; floating windows fixed
- `Tools/facade_detail_concepts.py` generates architectural detail plates from one
local reference photograph via OpenRouter image models (default
`google/gemini-2.5-flash-image`; the sibling project uses the same route in
`tools/generate_textures.py` — chat/completions with a text + image_url content
part, image returned as `message.images[].image_url.url`). Eight studies,
`--list` free, `--all` generates, `-vN` never overwrites, `.refgen.json` sidecar
per image, all output under gitignored `Art/reference/facade-details/`.
- **Prompt gotcha:** the model refuses when the prompt describes features the
reference photograph does not show ("the image you sent appears to be a
decorative architectural detail, not an entrance with steps"). Prompts must
describe the *reference subject* and only restage light/people/surface.
- `Tools/facade_detail_concepts.py` generates architectural detail plates from one local reference photograph via OpenRouter image models (default `google/gemini-2.5-flash-image`; the sibling project uses the same route in `tools/generate_textures.py` — chat/completions with a text + image_url content part, image returned as `message.images[].image_url.url`). Eight studies, `--list` free, `--all` generates, `-vN` never overwrites, `.refgen.json` sidecar per image, all output under gitignored `Art/reference/facade-details/`.
- **Prompt gotcha:** the model refuses when the prompt describes features the reference photograph does not show ("the image you sent appears to be a decorative architectural detail, not an entrance with steps"). Prompts must describe the _reference subject_ and only restage light/people/surface.
- **Output is real:** 1184×864 to 1344×768 PNGs, ~2 MB each.
- **Measured defect found and fixed:** hero window panes sat at Y −1.98 while the
mass front is Y −0.50 and the columns reach −1.02 — i.e. glazing floating 1.44 m
in front of the wall and ahead of the colonnade. Now `WINDOW_Y = −0.56`
(6 cm proud of the wall, behind the columns). Visible as dark openings in
`review-hero-facade-front`.
- Reminder: `.blend` is LFS read-only on checkout — `chmod u+w` or Blender fails
with "Cannot save blend file … not writable" (this bit again here).
- **Measured defect found and fixed:** hero window panes sat at Y −1.98 while the mass front is Y −0.50 and the columns reach −1.02 — i.e. glazing floating 1.44 m in front of the wall and ahead of the colonnade. Now `WINDOW_Y = −0.56` (6 cm proud of the wall, behind the columns). Visible as dark openings in `review-hero-facade-front`.
- Reminder: `.blend` is LFS read-only on checkout — `chmod u+w` or Blender fails with "Cannot save blend file … not writable" (this bit again here).
## 2026-11-09 — UE export hygiene: smoothing groups + facade collision
- **All our FBX exporters used `mesh_smooth_type="OFF"`**, which is why every Unreal
import logged "No smoothing group information was found for this mesh". The hero
now exports `"FACE"` and its round parts are shaded with
`shade_smooth_by_angle(35°)` before export: cylinders read as round instead of
faceted, and the import warning is gone (6 → 0).
- **The hero façade had no simple collision** (complex-only), so line traces and
capsule sweeps passed straight through it — the player could walk through the
wall of the demo. Caught by a new `-qaselftest` **facade occlusion probe** (40 m
sideways traces from the road centre at walking height).
- **Auto-generated convex collision on a 95-part 26 m façade is unusable**: it
produced coarse hulls that bulged into the street and the wall probe went
0/35 → 2/35. Rejected in favour of `UseComplexAsSimple` via
`UGigLineAssetTools.set_complex_collision_as_simple` (same reason the terrain
uses it), saved on the asset.
- Probe expectations must mirror the staged buildings: the facade probe now carries
explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so
a gap between buildings is not reported as a defect.
- **All our FBX exporters used `mesh_smooth_type="OFF"`**, which is why every Unreal import logged "No smoothing group information was found for this mesh". The hero now exports `"FACE"` and its round parts are shaded with `shade_smooth_by_angle(35°)` before export: cylinders read as round instead of faceted, and the import warning is gone (6 → 0).
- **The hero façade had no simple collision** (complex-only), so line traces and capsule sweeps passed straight through it — the player could walk through the wall of the demo. Caught by a new `-qaselftest` **facade occlusion probe** (40 m sideways traces from the road centre at walking height).
- **Auto-generated convex collision on a 95-part 26 m façade is unusable**: it produced coarse hulls that bulged into the street and the wall probe went 0/35 → 2/35. Rejected in favour of `UseComplexAsSimple` via `UGigLineAssetTools.set_complex_collision_as_simple` (same reason the terrain uses it), saved on the asset.
- Probe expectations must mirror the staged buildings: the facade probe now carries explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so a gap between buildings is not reported as a defect.
## 2026-11-09 — one FBX export preset instead of six copies
- The `bpy.ops.export_scene.fbx(...)` argument block was duplicated in six scripts
(hero, block frontage, north buildings, site, scale test, MPFB character), and
all six carried `mesh_smooth_type="OFF"` — hence the "No smoothing group
information was found" warning on every import.
- Now `Tools/fbx_export_preset.py` holds `ue_metric_v1` and `ue_character_v1`:
`preset_kwargs()` (bpy-free, unit-tested) plus `export_static()` /
`export_character()` wrappers that import `bpy` lazily. Every caller imports it
and passes `selection=True/False`; the seven duplicated blocks are gone.
- Callers in `Tools/` resolve the module with
`sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))`; the Blender-side
`Art/blender/site/export_site_fbx.py` computes the repo root
(`..`, `..`, `..`) and joins `Tools`. Getting that wrong silently inserts
`Tools/Tools` — check the import path when adding a caller.
- Verified end to end: scale test, hero, frontage (6/6), site (7/7) and north
buildings all re-exported through the preset, re-imported with **0 smoothing
warnings**, DemoMap rebuilt and `-qaselftest` all PASS (walk, transect 0 holes,
wall probe 0/35, facade probe 0/9).
- `mpfb_character_export.py` was not re-run: it needs the MPFB add-on on
`BLENDER_USER_SCRIPTS` ("Add-on not loaded: bl_ext.blender_org.mpfb"), which is
pre-existing and unrelated; the character is not in `0.1.x` gameplay.
- The `bpy.ops.export_scene.fbx(...)` argument block was duplicated in six scripts (hero, block frontage, north buildings, site, scale test, MPFB character), and all six carried `mesh_smooth_type="OFF"` — hence the "No smoothing group information was found" warning on every import.
- Now `Tools/fbx_export_preset.py` holds `ue_metric_v1` and `ue_character_v1`: `preset_kwargs()` (bpy-free, unit-tested) plus `export_static()` / `export_character()` wrappers that import `bpy` lazily. Every caller imports it and passes `selection=True/False`; the seven duplicated blocks are gone.
- Callers in `Tools/` resolve the module with `sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))`; the Blender-side `Art/blender/site/export_site_fbx.py` computes the repo root (`..`, `..`, `..`) and joins `Tools`. Getting that wrong silently inserts `Tools/Tools` — check the import path when adding a caller.
- Verified end to end: scale test, hero, frontage (6/6), site (7/7) and north buildings all re-exported through the preset, re-imported with **0 smoothing warnings**, DemoMap rebuilt and `-qaselftest` all PASS (walk, transect 0 holes, wall probe 0/35, facade probe 0/9).
- `mpfb_character_export.py` was not re-run: it needs the MPFB add-on on `BLENDER_USER_SCRIPTS` ("Add-on not loaded: bl_ext.blender_org.mpfb"), which is pre-existing and unrelated; the character is not in `0.1.x` gameplay.
## 2026-11-09 — v0.1.2 released
- Cornice finished: projecting corona over a **52-dentil course** (0.18 × 0.30 ×
0.22 m at 0.45 m centres, z 12.12–12.34, 0.25 m clear of the corona). Hero is now
3 044 tris across 148 parts, still 14.50 m. Verified: packaged `-qaselftest` all
PASS (transect 0 holes, wall probe 0/35, facade probe 0/9).
- `CHANGELOG.md` gets the `0.1.2` entry (added/changed/fixed/verified/limitations),
the `0.1.2` section is removed from `TODO.md`, `ROADMAP.md` status updated, tag
`v0.1.2` cut after the merge.
- Assumptions recorded rather than hidden: dentil spacing, base and abacus
dimensions are not resolvable in our reference photography.
- Cornice finished: projecting corona over a **52-dentil course** (0.18 × 0.30 × 0.22 m at 0.45 m centres, z 12.12–12.34, 0.25 m clear of the corona). Hero is now 3 044 tris across 148 parts, still 14.50 m. Verified: packaged `-qaselftest` all PASS (transect 0 holes, wall probe 0/35, facade probe 0/9).
- `CHANGELOG.md` gets the `0.1.2` entry (added/changed/fixed/verified/limitations), the `0.1.2` section is removed from `TODO.md`, `ROADMAP.md` status updated, tag `v0.1.2` cut after the merge.
- Assumptions recorded rather than hidden: dentil spacing, base and abacus dimensions are not resolvable in our reference photography.
## 2026-11-09 — elevation cameras levelled (straight verticals)
- archviz MUST: an elevation must not lean. `hero-facade-front` looked up ~4° from
4.2 m at a 7.6 m target; `demo-spawn-view` and `demo-street-level` were pitched
too. All three now put the camera at the target's height, so
`FindLookAtRotation` yields pitch 0 and roll 0 and verticals are parallel by
construction.
- Do not try to *measure* verticality from the rendered PNG with a naive edge
scan: banners, windows and the parapet produce different edge sets at different
heights and the pairwise match is meaningless (it reported a 418 px "drift").
Verify it geometrically in the camera list instead.
- archviz MUST: an elevation must not lean. `hero-facade-front` looked up ~4° from 4.2 m at a 7.6 m target; `demo-spawn-view` and `demo-street-level` were pitched too. All three now put the camera at the target's height, so `FindLookAtRotation` yields pitch 0 and roll 0 and verticals are parallel by construction.
- Do not try to _measure_ verticality from the rendered PNG with a naive edge scan: banners, windows and the parapet produce different edge sets at different heights and the pairwise match is meaningless (it reported a 418 px "drift"). Verify it geometrically in the camera list instead.
## 2026-11-09 — 0.1.3 packaging/setup fixes
- **`Tools/ue_common.py`**: `make_writable()` clears the read-only bit an LFS
`.umap` checks out with (otherwise `save_map()` returns False with no error and
the staged actors vanish), and `require_saved()` makes that failure visible.
`ue_hero_facade_import.py` and `ue_import_north_buildings.py` never did this —
which is why importing the hero appeared to succeed while saving nothing.
Verified by forcing `chmod u-w` on DemoMap first: the run logged
`map writable: chmod u+w … (was 0o444)`, then `save_map: True`, exit 0.
- Gotcha while adding it: `ue_hero_facade_import.py` had **no** `import os`/`sys`
(only `unreal`), so the sys.path line raised `NameError: name 'sys' is not
defined` inside the commandlet — an exit code of 255 with no obvious cause.
- Docs: `build.md` gained the current pipeline table (7 site FBXs, frontage, north,
hero, map), the `Builds/GigLineQA` archive, the packaged-log path, `-qaselftest`
usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and
documents the two optional API keys; architecture 07 + combined doc corrected the
build-output path.
- **`Tools/ue_common.py`**: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False with no error and the staged actors vanish), and `require_saved()` makes that failure visible. `ue_hero_facade_import.py` and `ue_import_north_buildings.py` never did this — which is why importing the hero appeared to succeed while saving nothing. Verified by forcing `chmod u-w` on DemoMap first: the run logged `map writable: chmod u+w … (was 0o444)`, then `save_map: True`, exit 0.
- Gotcha while adding it: `ue_hero_facade_import.py` had **no** `import os`/`sys` (only `unreal`), so the sys.path line raised `NameError: name 'sys' is not defined` inside the commandlet — an exit code of 255 with no obvious cause.
- Docs: `build.md` gained the current pipeline table (7 site FBXs, frontage, north, hero, map), the `Builds/GigLineQA` archive, the packaged-log path, `-qaselftest` usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and documents the two optional API keys; architecture 07 + combined doc corrected the build-output path.
## 2026-11-09 — 0.1.4 materials + architecture-camera pass
- **Material graphs**: `ensure_material()` now supports masonry coursing (`courses_m`) and roughness variation. **UE's `WorldPosition` is in CENTIMETRES**, so a course height in metres must be ×100 — the first attempt divided by 0.55 and produced coursing 100× too fine to see.
- **`connect_material_expressions` returns False instead of raising**, so an unknown pin name leaves an input unconnected and the material silently compiles with a default (a mask of 0 is invisible). All links now go through a `link()` helper that logs failures. There is also **no `MaterialExpressionLess`**; use `Frac → Divide → OneMinus → Clamp` for a soft threshold band, or `MaterialExpressionStep`/`SmoothStep` if a hard one is wanted.
- **Material assignment was slot-0-only**, which on the hero is the _banner_ — the stone, shadowed stone and glazing kept their flat Blender materials. Assignment is now per slot, keyed on the imported slot material's name (`glass`/`banner`/`stoneshadow`/`stone`/`roof`/`wall`). Hero: 4/4 slots.
- New materials: `M_FacadeStoneShadow` (same coursing, a stop darker), `M_FacadeGlass` (roughness 0.14, specular 0.62 ≈ IOR 1.5 expressed without a refraction pass), `M_FacadeBanner` (the one saturated surface).
- **Camera FOV**: levelling the elevation cameras left the building small in frame (39 % sky). Added optional per-shot `ShotFieldOfView` to `AGigLineShotActor` and a 4th element on the shot tuples; a long lens (42–55°) fills the frame from across the street while the camera stays level, so verticals stay parallel. `review-hero-facade-front` sky 39 → 20 %, `demo-spawn-view` 23 → 16 %.
## 2026-11-09 — facade texture maps (0.1.4)
- `Tools/generate_facade_textures.py` writes deterministic 512² base-colour and roughness maps (ashlar stone, stucco, asphalt, concrete) with a pure-zlib PNG encoder and hash-based value noise — no numpy/Pillow, ~14 s for all eight, and byte-identical on a re-run (checked by md5sum).
- `ue_build_demo_map.py` imports them to `/Game/Hollywood/Textures` and samples them **world-aligned**: WorldPosition (cm) → ×(1/tile_cm) → ComponentMask(R,G) → TextureSample UVs. `tile_cm` per material: stone 440, stucco 260, asphalt 900, concrete 250.
- Gotchas: `MaterialExpressionTextureSample`'s UV input pin is **"UVs"**, and the output pin for colour is **"RGB"** / roughness **"R"**. `ComponentMask` pins are R/G/B/A booleans. A textured material skips the graph coursing on purpose — overlaying both would double the course pattern.
- The script needs `ROOT` in `ue_build_demo_map.py` and `sys` imported; forgetting either fails with `NameError` and an exit code of 0 from the commandlet (always read the result file under /tmp/gigline).
## 2026-11-09 — façade module kit (0.1.4)
- `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay** and **3.10 m storey**: pilaster, sill, lintel, window bay, shopfront bay, door bay, cornice, parapet + coping = 16 meshes, exported per-object with `fbx_export_preset` plus a JSON part report (dims + evidence per piece).
- `Tools/ue_import_facade_kit.py` → `/Game/Hollywood/Kit/*`, complex-as-simple collision (a convex hull would fence off the shopfront glazing).
- `Tools/ue_apply_facade_kit.py` lays the kit out from each building's own bbox: `bays = round(width / 496 cm)`, storeys from height minus the 310 cm ground floor. **638 pieces** over six frontage buildings + both north buildings.
- **Bug worth remembering:** the first window piece was modelled entirely at +Y (into the wall), so every window sat _inside_ the solid blockout wall and was invisible in every render while the placement maths looked right. The kit now projects the surround 3 cm proud with the glazing 6 cm behind its face — a solid-wall blockout cannot carry a real recess, so the piece has to project.
- Blender gotcha: clearing the scene _including materials_ invalidates handles already taken ("StructRNA of type Material has been removed"); clear objects and orphan meshes only, and create the material after the clear.
- The kit meshes are spawned at the world origin with their position baked into the mesh (like the frontage exports), so placement uses the bbox directly and must not add the actor location.
## 2026-11-09 — CRITICAL: blockout must be regenerated with --origin
- Regenerating the blockout in 0.1.2 (for the 3.1 m storey height) was run **without `--origin`**, so Blosm centred on the extract instead of fitting the project frame. The whole frontage shifted about **119 m in X and 2.4 m in Y**, and the hero ended up outside the 6902/6838 gap — the "verified placement" was undone silently, and every render still looked like a plausible block.
- Correct invocation (README documents it, and the affine fit prints `frame correction residual: max 0.0 cm`): `blosm_blockout.py --osm … --bounds 34.1006,-118.3422,34.1020,-118.3370 --level-height 3.1 --origin 34.101545,-118.338718 --assets-dir … --out …`
- After re-export + re-import, the frontage spans are back to the verified values: 6922 −24276..−17700, 6902 −17749..−14591, **hero −14644..−12028 between them**, theatre −12082..−8210, and the north buildings at −22889..−18442 / −27071..−22799.
- **`ue_import_north_buildings.py` also lacked `import sys`**, so its sys.path line raised NameError and the import silently did nothing (exit 255). Same trap as the hero importer: these scripts imported only `unreal`.
- Lesson: after any blockout regeneration, probe the staged buildings' world X spans and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before trusting a render. A shift of this size is invisible in a screenshot.
## 2026-11-09 — kit naming + topology validation (blender-modeler checklist)
- Kit assets now follow the Unreal convention: the Blender object keeps its readable name (`Kit_Pilaster`), the exported asset and file are `SM_Kit_Pilaster`. The kit loader strips the `SM_` prefix so the layout code still addresses pieces by short name.
- `facade_kit_build.py::validate()` runs the blender-modeler cleanup checklist on every piece before handoff — non-manifold edges, loose vertices, triangle count — and records the result in the part report. Current kit: 16 pieces, **all manifold, no loose geometry, 12 tris each**.
- `ue_import_facade_kit.py` now deletes kit assets that the current build no longer produces, so a rename cannot leave two generations of the same piece in the folder (the rename from `Kit_*` to `SM_Kit_*` did exactly that until it did).
## 2026-11-09 — visual comparison loop; screenshots are demo-only
- `Tools/visual_loop.py` closes the reference → render → feedback loop. `--verify` gates captures against `Tools/visual_targets.json` (blank, tone-less, wrong-size fail); `--compare` measures luma, RMS contrast, edge density, palette distance and a 3x3 luma grid against the reference and prints a 0–1 score; `--iteration N` writes `Art/reference/iterations/N/{metrics.json,feedback.md}`. The feedback names the skill to consult, so the next modelling pass starts from a measurement rather than an opinion.
- **Iterations are gitignored** (`/Art/reference/iterations/`); only confirmed shots are promoted into `Screenshots/`. A test asserts the ignore rule and that every target is a demo-map shot.
- `Screenshots/` now holds **demo-map captures only** — the review staging map is no longer rendered for review. `render_screenshots.sh --verify` renders the demo map, collects the shots and runs the verification gate in one step.
- **PIL gotchas:** `FIND_EDGES` leaves its own border ring at 128 (236/3600 px on a 60x60 frame = 6.5% "detail" on a perfectly flat image), so crop **after** filtering — cropping before does not help, the new border gets the same treatment. Palette shares must divide by the full-image area, not the cropped one.
- First comparison (iteration 2): spawn view 0.70, street level 0.35, crossing 0.41, north buildings 0.34. Edge density is the dominant gap everywhere (0.04–0.11 against 0.20–0.32 in the references) — the render still lacks the relief the photographs carry, which is exactly what the `0.1.4` façade work is for.
## 2026-11-09 — theatre marquee + blade sign
- `Tools/theatre_trim_build.py` builds the El Capitan Theatre's signature features in **world coordinates**: marquee canopy 12.0 × 2.60 m at a 4.60 m soffit, illuminated fascia, recessed soffit, vertical blade sign 1.30 × 5.20 m with frame and bracket. `Tools/ue_stage_theatre_trim.py` imports to `/Game/Hollywood/Theatre` and stages one actor per piece (single spawn at the origin — no placement arithmetic).
- **Two placement bugs, both invisible in a render:**
1. **Axis sign.** Blender +Y is Unreal −Y, so a face at Unreal Y +1001 is Blender Y −10.01. Using +10.01 put the whole marquee on the far side of the boulevard.
2. **Absolute Z.** Trim heights were absolute while the pavement at the theatre is 4.56 m above the project datum, so the canopy hung 5 cm above the pavement — the QA wall probe caught it as a knee-height blocker. `GROUND_Z` now offsets every height.
- Wall probe: a scan row at Y 1000 sat ~1 cm from the frontage building line and grazed the theatre wall once the kit's proud shopfronts went in. Rows now sample the street envelope only (−1400, −167, 800, −1382, 718).
## 2026-11-09 — 0.1.4 signed off; 0.1.5 created for the carried geometry
- Owner reviewed the four demo captures locally and approved the set. Signoff recorded in `Docs/reviews/0.1.4-review.md` (7/7 items) and in the per-shot `Approved` column of `Screenshots/README.md`.
- The three geometry items that `0.1.4` did not finish — retail neighbours from their own references, hero fluting/motto band, blockout cleaning — move to a new **`0.1.5` Façade finishing** patch rather than growing this one, plus a much-needed **1440p performance re-measure** (625 kit actors have never been measured; `0.1.0` measured 414 fps).
- Decision recorded because it sets a precedent: finish and sign off a version when its _stated_ scope is done, and carry leftover scope into the next patch — do not let a patch accumulate unmeasured geometry.