feat(hero): banner lettering, re-render the three staging shots; defer the rest to 0.9.0
Banner lettering (JIMMY KIMMEL LIVE): new Tools/gen/generate_banner_texture.py draws the exact title with Pillow + Liberation Sans Bold - an image model would mangle a trademark string, and Qwen-Image 2.1 is research-only. Gold JIMMY / KIMMEL / LIVE stacked on a crimson ground. ue_hero_facade_import.py generalises import_albedo to import_texture and wires the banner into M_Facade_Banner_Ref as Base Color (tiling 1.0), replacing the flat dark tint with a brighter red per the owner's 'less dark, more bright'. Re-rendered all three ReviewMap staging shots (front, oblique, portals) and updated their Screenshots/README.md status rows. The hero facade rework is now closed at 'surface detail that reads at 45 m'. The remaining elevation work - volute capital depth, lattice grille depth, and the full realism pass - is deferred to 0.9.0 per the owner, and the 0.9.0 hero item records the hand-off.
This commit is contained in:
1 parent
bc9fed8e11
commit
8c34eaece0
26 files changed
+171
-41
No files matched your search
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.
Binary file not shown.
@@ -680,3 +680,18 @@ A visual change needs **build → import → render**, never just a render: `ren
|
||||
- **The `seat_on_front` table had a dead duplicate** — `Hero_MottoBand` appeared at both
|
||||
0.06 and 0.40, and the first match (0.06) won, so the 0.40 was silently ignored. That
|
||||
is why "the band is buried" looked fixed in the table while the build kept burying it.
|
||||
|
||||
## 2026-11-10 — Hero facade rework closed; remaining detail deferred to 0.9.0
|
||||
|
||||
- The hero facade rework (split out of 0.3.0) is **closed at "surface detail that reads
|
||||
at 45 m"**. Done and committed: carved inscription, stepped medallions, light motto
|
||||
band + proud surround (all three had been buried inside the parapet), and gold
|
||||
JIMMY KIMMEL LIVE banner lettering.
|
||||
- **Banner lettering is procedural, not ComfyUI.** `Tools/gen/generate_banner_texture.py`
|
||||
draws the exact title with Pillow + Liberation Sans Bold. An image model (Qwen-Image
|
||||
2.1) would mangle a trademark string and is research-only, so for a *known* string a
|
||||
font rasteriser is strictly better than txt2img. Reserve ComfyUI/Qwen for things that
|
||||
need a model, not for literal text.
|
||||
- **Deferred to 0.9.0 (owner's instruction):** volute capital depth, lattice grille
|
||||
depth, and the full realism pass (banded rustication, measured materials, texel density,
|
||||
LOD/collision). The 0.9.0 "Hero façade (6840)" item records the hand-off.
|
||||
@@ -5,7 +5,7 @@ Two reviews in one file:
|
||||
- **ReviewMap** stages imported assets on their own — individual objects, the Blender import, the camera angles and the lighting comparison. Nothing from the game map is involved.
|
||||
- **DemoMap** is the actual game map as it is being developed: the street, the buildings, the player and the vehicle in their real placement.
|
||||
|
||||
Both capture passes are produced by [`Tools/review/render_screenshots.sh`](../Tools/render_screenshots.sh); the asset renders come from [`Tools/blender/blender_screenshots.py`](../Tools/blender_screenshots.py).
|
||||
Both capture passes are produced by [`Tools/review/render_screenshots.sh`](../Tools/review/render_screenshots.sh); the asset renders come from [`Tools/blender/blender_screenshots.py`](../Tools/blender/blender_screenshots.py).
|
||||
|
||||
## Status
|
||||
|
||||
@@ -16,9 +16,9 @@ Both capture passes are produced by [`Tools/review/render_screenshots.sh`](../To
|
||||
| 1 | ReviewMap |  | [x] | approved |
|
||||
| 2 | ReviewMap |  | [x] | approved |
|
||||
| 3 | ReviewMap |  | [x] | approved |
|
||||
| 4 | ReviewMap |  | [ ] | re-rendered with the carved inscription: three lines of incised letter marks across the attic band, seated on the parapet face so they are not hidden behind it; the stone reads pale marble-limestone. Still open: medallions do not read, banner lettering, capital and grille depth |
|
||||
| 5 | ReviewMap |  | [ ] | re-rendered with the carved inscription: three lines of incised letter marks across the attic band, seated on the parapet face so they are not hidden behind it; the stone reads pale marble-limestone. Still open: medallions do not read, banner lettering, capital and grille depth |
|
||||
| 6 | ReviewMap |  | [ ] | re-rendered with the carved inscription: three lines of incised letter marks across the attic band, seated on the parapet face so they are not hidden behind it; the stone reads pale marble-limestone. Still open: medallions do not read, banner lettering, capital and grille depth |
|
||||
| 4 | ReviewMap |  | [ ] | re-rendered with the full attic detail: carved inscription (seated on the parapet face), two stepped medallions (dark rim / raised light field / dark boss, moved out of the text span), a light motto band with a proud moulded surround (all three had been buried inside the parapet), and gold JIMMY KIMMEL LIVE lettering on the crimson banners. Remaining: capital and grille depth - deferred to `0.9.0`. |
|
||||
| 5 | ReviewMap |  | [ ] | re-rendered with the full attic detail: carved inscription (seated on the parapet face), two stepped medallions (dark rim / raised light field / dark boss, moved out of the text span), a light motto band with a proud moulded surround (all three had been buried inside the parapet), and gold JIMMY KIMMEL LIVE lettering on the crimson banners. Remaining: capital and grille depth - deferred to `0.9.0`. |
|
||||
| 6 | ReviewMap |  | [ ] | re-rendered with the full attic detail: carved inscription (seated on the parapet face), two stepped medallions (dark rim / raised light field / dark boss, moved out of the text span), a light motto band with a proud moulded surround (all three had been buried inside the parapet), and gold JIMMY KIMMEL LIVE lettering on the crimson banners. Remaining: capital and grille depth - deferred to `0.9.0`. |
|
||||
| 7 | ReviewMap |  | [x] | |
|
||||
| 8 | ReviewMap |  | [x] | approved |
|
||||
| 9 | ReviewMap |  | [x] | approved |
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -225,9 +225,11 @@ Open:
|
||||
- [x] Carved inscription: `inscription()` in `Tools/blender/hero_facade_build.py` lays 112 letter marks in three lines across the attic, in the shadow stone so they read at 45 m. They had to be **seated on the parapet face** (`FRONT_Y + 0.40`): the parapet is 1.2 m deep and stands in front of the wall plane, so marks at the wall plane were hidden behind it.
|
||||
- [x] Medallion relief that reads at 45 m. **Measured first, then fixed:** the discs were not just flat - they were **buried**. `seat_on_front` put `Hero_Medallion` at offset 0.08 → front face Y −0.42, while the parapet (1.2 m deep) stands at Y −0.15, so both medallions were inside the parapet and never rendered (the motto band and surround have the same bug at offset 0.06). Fixed: three stepped layers per medallion — `Hero_MedallionRim` (0.55 m, dark, offset 0.36 → Y −0.14), `Hero_MedallionField` (0.38 m, light, offset 0.43 → Y −0.07), `Hero_MedallionBoss` (0.15 m, dark, offset 0.50 → Y 0.00) — a 7 cm step each way so the rim/field/boss read as relief. Moved from ±6.2 (inside the inscription text, ±6.87) to ±9.8. Rendered and judged; the two-model judge is advisory-pessimistic (`no` 0.93) and does not flag medallions any more. Owner review pending.
|
||||
- [x] Motto band and surround — same burial bug (offset 0.06 → front Y −0.44). The band is now **light stone** (was `stone_dark`, which would have swallowed the dark lettering once visible), recessed at offset 0.34 (Y −0.16), with the moulded surround proud at offset 0.43 (Y −0.07) and the inscription at 0.40 (Y −0.10); the dead duplicate `Hero_MottoBand 0.40` entry is removed. Rendered and judged; the judge (`no` 0.93) no longer flags the band, only the stone warmth/column fluting the owner has already directed lighter. Owner review pending.
|
||||
- [ ] Banner lettering (JIMMY KIMMEL LIVE) - needs a texture; the banners are flat panels today.
|
||||
- [ ] Crimson banner tone and the JIMMY KIMMEL LIVE lettering.
|
||||
- [ ] Re-render the three ReviewMap staging shots and put them back in the status table for review.
|
||||
- [x] Banner lettering (JIMMY KIMMEL LIVE). New `Tools/gen/generate_banner_texture.py` draws the exact title with Pillow + Liberation Sans Bold (an image model would mangle a trademark string, and Qwen-Image 2.1 is research-only). Gold `JIMMY` / `KIMMEL` / `LIVE` stacked on a crimson ground, 512 × 2304. `ue_hero_facade_import.py` generalised `import_albedo` → `import_texture` and wires the banner into `M_Facade_Banner_Ref` as Base Color (tiling 1.0). Owner review pending.
|
||||
- [x] Crimson banner tone + lettering. The texture carries the red ground (186,24,24 — brighter than the old dark tint, per the owner's "less dark, more bright") and the gold lettering, so the flat tint is replaced outright.
|
||||
- [x] Re-render the three ReviewMap staging shots and put them back in the status table for review. All three (`review-hero-facade-front`, `review-hero-facade-oblique`, `review-hero-portals`) regenerated 2026-11-10 with the full attic detail, and the `Screenshots/README.md` status table updated. Owner review pending.
|
||||
|
||||
**Deferred to `0.9.0` (owner, 2026-11-10).** This pass is closed at "surface detail that reads at 45 m". The remaining elevation work is a `0.9.0` pass, not another patch here: **volute capital depth** (the capitals are block-only, the reference has strong shadow lines), **lattice grille depth** (the window bays are plain panels with bars, not the dark metal lattice), and the full realism pass (heavy banded rustication, measured material library, texel-density and LOD/collision per building). The `0.9.0` hero item records the hand-off.
|
||||
|
||||
## 0.3.0 — Chaos vehicle, enter/exit, closed driving route (weeks 10–13)
|
||||
|
||||
@@ -485,7 +487,7 @@ Suggested blender skills: **archviz** (real-world scale, façade composition, el
|
||||
|
||||
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): raise to this pass's detail level — **volute capital depth** and **lattice grille depth** (both deferred from the 2026-11-10 hero rework, which closed the inscription/medallions/motto-band/banner surface), window rhythm behind the colonnade, and the heavy banded rustication the elevation still lacks. The motto band, medallions and banner lettering are already done and carry over.
|
||||
- [ ] 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.
|
||||
|
||||
@@ -105,6 +105,7 @@ Scripts resolve the repository root as `../..` from their own folder and put the
|
||||
| [`gen/comfyui_generate.py`](gen/comfyui_generate.py) | Drives the **local** ComfyUI at `http://127.0.0.1:8188` (Stability Matrix install). `models` lists checkpoints; `txt2img` renders through the HTTP API; **`texture` renders material sheets with the project's documented texture workflows** (see below). Every run writes a provenance sidecar (model, prompt, seed, settings, full graph). Two candidates by default — same prompt and seed, both scored by high-frequency detail, the winner takes `--out`, the loser is kept beside it. Local and human-triggered; no credential. |
|
||||
| [`gen/generate_facade_textures.py`](gen/generate_facade_textures.py) | Procedural, deterministic facade texture maps (ashlar base colour + roughness, stucco, asphalt, concrete) written with a pure-zlib PNG encoder — no model, no network, byte-identical on every run. |
|
||||
| [`gen/generate_character_textures.py`](gen/generate_character_textures.py) | Procedural character texture maps, same deterministic approach as the facade set. |
|
||||
| [`gen/generate_banner_texture.py`](gen/generate_banner_texture.py) | Draws the hero-facade banner (`JIMMY KIMMEL LIVE` in gold on crimson) with Pillow + a system font — exact text, deterministic, no model. An image model would mangle a trademark string, and Qwen-Image 2.1 is research-only, so this is the lettering path. `--out Art/textures/generated/T_Banner_KIMMEL.png` by default. |
|
||||
| [`gen/facade_detail_concepts.py`](gen/facade_detail_concepts.py) | Generates **frontal building plates** (whole elevation, entrance in its facade, or building with neighbours) from a local reference photograph with an image model (OpenRouter); close-up detail crops are deliberately not requested because they cannot be measured for proportion. `--list` is free and shows which studies have their reference present; `--all`/`--only <name>` generates. Never overwrites (writes `-v2`, `-v3`), writes a `.refgen.json` sidecar per image (model, prompt, source photo, SHA-256), and puts everything under `Art/reference/facade-details/` — gitignored, local-only, never shipped. Costs money per image, so it is never automatic. |
|
||||
| [`review/judge_screenshot.py`](review/judge_screenshot.py) | (see Visual review) |
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the JIMMY KIMMEL LIVE banner texture for the hero facade.
|
||||
|
||||
The three event banners are flat panels; the lettering is text and cannot be
|
||||
read as geometry at 45 m. An image model would mangle a specific trademark
|
||||
string, so the text is drawn here with Pillow and a system font instead:
|
||||
deterministic, exact, and (unlike Qwen-Image 2.1) not research-only.
|
||||
|
||||
The banner face is 1.35 m wide x 6.2 m tall (aspect 1:4.59). The texture is
|
||||
written at 512 x 2304 (aspect 1:4.5), a 2% stretch that is invisible at the
|
||||
review distance.
|
||||
|
||||
python3 Tools/gen/generate_banner_texture.py
|
||||
python3 Tools/gen/generate_banner_texture.py --out Art/textures/generated/T_Banner_KIMMEL.png
|
||||
|
||||
Requires Pillow (host-only tool, not a gate; standard library is not enough to
|
||||
rasterise a font). The text is a trademark reproduced for internal development
|
||||
review only - record it in THIRD_PARTY_NOTICES.md before any public build.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
# Banner face aspect: 1.35 m wide x 6.2 m tall. 512 x 2304 keeps that to within 2 %.
|
||||
WIDTH = 512
|
||||
HEIGHT = 2304
|
||||
|
||||
# sRGB colours. The banner is a deep event red, the lettering is gold; both match the
|
||||
# photographs (and the owner's "less dark, more bright" direction for the elevation).
|
||||
BANNER = (186, 24, 24)
|
||||
GOLD = (232, 198, 90)
|
||||
|
||||
FONT_BOLD = "/usr/share/fonts/liberation-sans-fonts/LiberationSans-Bold.ttf"
|
||||
FONT_REGULAR = "/usr/share/fonts/liberation-sans-fonts/LiberationSans-Regular.ttf"
|
||||
|
||||
# Three stacked words, as the real vertical banners arrange the title. `size` is the
|
||||
# font height in texture pixels; the block is centred vertically in the banner.
|
||||
LINES = [
|
||||
("JIMMY", 150, FONT_BOLD),
|
||||
("KIMMEL", 150, FONT_BOLD),
|
||||
("LIVE", 120, FONT_REGULAR),
|
||||
]
|
||||
LINE_GAP = 70 # pixels between baselines beyond the font ascent
|
||||
|
||||
|
||||
def _font(path: str, size: int) -> ImageFont.FreeTypeFont:
|
||||
if not os.path.exists(path):
|
||||
raise SystemExit(f"font missing: {path}")
|
||||
return ImageFont.truetype(path, size)
|
||||
|
||||
|
||||
def draw_banner(out: str) -> None:
|
||||
img = Image.new("RGB", (WIDTH, HEIGHT), BANNER)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
fonts = [(text, size, _font(path, size)) for text, size, path in LINES]
|
||||
|
||||
# Measure the whole block so it sits centred vertically in the 6.2 m banner.
|
||||
ascents = [f.getbbox(text)[3] for text, _, f in fonts]
|
||||
block_h = sum(ascents) + LINE_GAP * (len(fonts) - 1)
|
||||
y = (HEIGHT - block_h) // 2
|
||||
|
||||
for text, size, font in fonts:
|
||||
bbox = font.getbbox(text)
|
||||
text_w = bbox[2] - bbox[0]
|
||||
x = (WIDTH - text_w) // 2 - bbox[0]
|
||||
draw.text((x, y), text, font=font, fill=GOLD)
|
||||
y += bbox[3] + LINE_GAP
|
||||
|
||||
os.makedirs(os.path.dirname(os.path.abspath(out)) or ".", exist_ok=True)
|
||||
img.save(out)
|
||||
print(f"wrote {out} ({WIDTH}x{HEIGHT})")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--out", default="Art/textures/generated/T_Banner_KIMMEL.png")
|
||||
args = parser.parse_args()
|
||||
draw_banner(args.out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -47,6 +47,8 @@ def log(msg):
|
||||
# reference facade is coursed rubble-ashlar, not a flat tint, and a texture is what carries that at 45 m.
|
||||
FACADE_ALBEDO = "Art/textures/generated/T_FacadeAshlar_AI.png"
|
||||
FACADE_ALBEDO_ASSET = "T_FacadeAshlar_AI"
|
||||
BANNER_ALBEDO = "Art/textures/generated/T_Banner_KIMMEL.png"
|
||||
BANNER_ALBEDO_ASSET = "T_Banner_KIMMEL"
|
||||
TEXTURE_DIR = "/Game/Hollywood/Textures"
|
||||
|
||||
FACADE_TINTS = {
|
||||
@@ -67,26 +69,26 @@ FACADE_TINTS = {
|
||||
}
|
||||
|
||||
|
||||
def import_albedo():
|
||||
"""Import the generated ashlar albedo, if it is on disk. Returns the asset or None."""
|
||||
def import_texture(rel_path, asset_name):
|
||||
"""Import a generated texture, if it is on disk. Returns the asset or None."""
|
||||
# unreal.Paths.project_dir() is Game/GigLine, not the repository root: the textures live beside Art/,
|
||||
# two levels above this file.
|
||||
# Tools/unreal/<this file> -> repository root is three levels up.
|
||||
repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
path = os.path.join(repo_root, FACADE_ALBEDO)
|
||||
path = os.path.join(repo_root, rel_path)
|
||||
if not os.path.exists(path):
|
||||
log(f"albedo missing: {path}")
|
||||
log(f"texture missing: {path}")
|
||||
return None
|
||||
task = unreal.AssetImportTask()
|
||||
task.filename = path
|
||||
task.destination_path = TEXTURE_DIR
|
||||
task.destination_name = FACADE_ALBEDO_ASSET
|
||||
task.destination_name = asset_name
|
||||
task.automated = True
|
||||
task.save = True
|
||||
task.replace_existing = True
|
||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||
asset = unreal.EditorAssetLibrary.load_asset(f"{TEXTURE_DIR}/{FACADE_ALBEDO_ASSET}")
|
||||
log(f"albedo imported: {asset.get_path_name() if asset else 'FAILED'}")
|
||||
asset = unreal.EditorAssetLibrary.load_asset(f"{TEXTURE_DIR}/{asset_name}")
|
||||
log(f"texture imported: {asset.get_path_name() if asset else 'FAILED'}")
|
||||
return asset
|
||||
|
||||
|
||||
@@ -102,7 +104,8 @@ def tint_imported_materials():
|
||||
and the owner's review was that the elevation read near-white grey.
|
||||
"""
|
||||
library = unreal.MaterialEditingLibrary
|
||||
albedo = import_albedo()
|
||||
albedo = import_texture(FACADE_ALBEDO, FACADE_ALBEDO_ASSET)
|
||||
banner_tex = import_texture(BANNER_ALBEDO, BANNER_ALBEDO_ASSET)
|
||||
built = 0
|
||||
for base, rgb in FACADE_TINTS.items():
|
||||
path = f"{DEST}/{base}_Ref"
|
||||
@@ -114,6 +117,7 @@ def tint_imported_materials():
|
||||
material, unreal.MaterialExpressionConstant3Vector, -600, 200)
|
||||
colour.set_editor_property("constant", unreal.LinearColor(rgb[0], rgb[1], rgb[2], 1.0))
|
||||
source = colour
|
||||
kind = "tint"
|
||||
if albedo is not None and base in ("M_Facade_Stone", "M_Facade_StoneShadow"):
|
||||
sample = library.create_material_expression(
|
||||
material, unreal.MaterialExpressionTextureSampleParameter2D, -900, -100)
|
||||
@@ -130,11 +134,25 @@ def tint_imported_materials():
|
||||
library.connect_material_expressions(sample, "RGB", multiply, "A")
|
||||
library.connect_material_expressions(colour, "", multiply, "B")
|
||||
source = multiply
|
||||
kind = "albedo x tint"
|
||||
elif banner_tex is not None and base == "M_Facade_Banner":
|
||||
# The banner texture carries both the red ground and the gold lettering, so it
|
||||
# replaces the tint outright (tiling 1.0, not the 6x stone coursing).
|
||||
sample = library.create_material_expression(
|
||||
material, unreal.MaterialExpressionTextureSampleParameter2D, -900, -100)
|
||||
sample.set_editor_property("parameter_name", f"Banner_{base}")
|
||||
sample.set_editor_property("texture", banner_tex)
|
||||
sample.set_editor_property("sampler_type", unreal.MaterialSamplerType.SAMPLERTYPE_COLOR)
|
||||
uvs = library.create_material_expression(
|
||||
material, unreal.MaterialExpressionTextureCoordinate, -1150, -100)
|
||||
library.connect_material_expressions(uvs, "", sample, "UVs")
|
||||
source = sample
|
||||
kind = "banner"
|
||||
library.connect_material_property(source, "", unreal.MaterialProperty.MP_BASE_COLOR)
|
||||
library.recompile_material(material)
|
||||
saved = unreal.EditorAssetLibrary.save_loaded_asset(material)
|
||||
built += 1
|
||||
log(f"{base}_Ref: {'albedo x tint' if source is not colour else 'tint'} {rgb}, saved {saved}")
|
||||
log(f"{base}_Ref: {kind} {rgb}, saved {saved}")
|
||||
log(f"authored {built} facade materials")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user