Map 1: rebuild Hollywood as the El Capitan landmark
Full build / Import + headless tests (push) Successful in 37s
Full build / Full desktop build + optional release (push) Skipped

Replace the generic boulevard facade with a stylized 6840 Hollywood Blvd block: six Ionic columns, eight stone steps, Masonic frieze/medallions, event banners, side doors, El Capitan Theatre and retail neighbours, Ovation Hollywood arcade, Walk of Fame and four-lane street markings.

Add the Day and Night Hollywood venue-menu rows. Night uses a phase-time offset so a full-length shift stays in LATE while the clock starts at zero; late routine traffic keeps the street populated and the offset round-trips through checkpoint restore.

Retune local-light falloff to linear attenuation, add a cool north sky fill, and use the smooth limestone facade texture. Refresh the staged gallery and map references; add geometry-budget, menu, night-offset and late-traffic tests.
This commit is contained in:
zwitschi committed 2026-11-02 12:34:48 +01:00
1 parent 3f335481d6
commit 74dc1fdc1d
50 files changed
+1176 -100

No files matched your search

+691 -34
View File
@@ -8,6 +8,7 @@ const T_BRICK := preload(PICK_DIR + "brick_facade.png")
const T_CONCRETE := preload(PICK_DIR + "sidewalk_pavers.png")
const T_ASPHALT := preload(PICK_DIR + "asphalt.png")
const T_AWNING := preload(PICK_DIR + "awning_canvas.png")
const T_LIMESTONE := preload(PICK_DIR + "stone_steps.png")
## One skyline panorama per shift phase (realism pass): hazy daylight, warm dusk
## with lit windows, and a night city. The old single strip was a black silhouette.
const T_SKYLINE_DAY := preload(PICK_DIR + "skyline_day.png")
@@ -44,6 +45,10 @@ var _door_indicator_mat: StandardMaterial3D = null
## Street lamps and signage so night mode can switch them on.
var _street_lamps: Array[MeshInstance3D] = []
var _lamp_omni: Array[OmniLight3D] = []
## Low-energy practical washes on the hero facade; kept separate from the street
## lamps so a close omni does not blow out the limestone at night.
var _accent_omni: Array[OmniLight3D] = []
var _ovation_sign: Label3D = null
var _lamp_bulb_mats: Array[StandardMaterial3D] = []
var marquee_label: Label3D = null
var _marquee_emissive: Array[StandardMaterial3D] = []
@@ -66,23 +71,55 @@ const LIBRARY_TRIANGLE_BUDGET := 60000
## the following tasks.
const VENUE_SPECS := {
"hollywood_building": {
"display_name": "Hollywood Boulevard Building",
"facade_width": 40.0, "facade_height": 18.0, "facade_depth": 4.0,
"facade_center_z": -6.0, "facade_texture": T_BRICK,
"facade_tint": Color(0.86, 0.85, 0.83), "facade_role": "brick",
"window_columns": [-17.0, -13.6, -10.2, -6.8, 6.8, 10.2, 13.6, 17.0],
"pilaster_columns": [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3],
"window_rows": [9.2, 12.4],
"stair_width": 8.0, "stair_count": 4, "stair_tread": 0.6, "stair_height": 0.16,
"sidewalk_width": 30.0, "sidewalk_depth": 8.0,
"street_width": 120.0, "street_depth": 26.0,
"display_name": "Hollywood Boulevard — El Capitan Entertainment Centre",
## The real 6840 Hollywood Blvd building: a 29.6 m wide, 14.5 m high
## Neoclassical temple facade with six Ionic columns, eight stone steps,
## a recessed central entrance and two street-level side doors.
"architecture": "masonic",
"facade_width": 29.6, "facade_height": 14.5, "facade_depth": 8.0,
"facade_center_z": -8.0, "facade_texture": T_LIMESTONE,
"facade_tint": Color(0.88, 0.86, 0.82), "facade_role": "stone",
"window_columns": [], "window_rows": [],
"stair_width": 12.0, "stair_count": 8, "stair_tread": 0.38, "stair_height": 0.10,
"landing_depth": 4.0, "landing_center_z": -1.8,
"sidewalk_width": 34.0, "sidewalk_depth": 12.0,
"street_width": 120.0, "street_depth": 26.0, "street_front_z": 12.0,
"nav_min": Vector2(-18.0, -0.6), "nav_max": Vector2(18.0, 20.0),
"guard_spawn": Vector3(0.0, 0.1, -2.0),
"guard_spawn": Vector3(0.0, 1.0, -2.0),
"features": {
"marquee": true, "skyline": true, "handrail": true, "sedan": true,
"wall_lanterns": true, "palms": true, "street_trees": true,
"marquee": false, "skyline": true, "handrail": false, "sedan": false,
"wall_lanterns": true, "palms": true, "street_trees": false,
"street_lamps": true, "crosswalk": true, "bulb_canopy": false,
"masonic_facade": true, "walk_of_fame": true, "neighbours": true,
"opposite_block": true, "hollywood_lane_markings": true,
},
"street_lamp_positions": [
Vector3(-13.2, 0.18, 10.9), Vector3(13.2, 0.18, 10.9),
],
"prop_placements": [
{"zone": "entrance", "prop": "bollard", "x": -7.2, "z": 5.5},
{"zone": "entrance", "prop": "bollard", "x": -8.6, "z": 5.5},
{"zone": "entrance", "prop": "bollard", "x": 7.2, "z": 5.5},
{"zone": "entrance", "prop": "bollard", "x": 8.6, "z": 5.5},
{"zone": "entrance", "prop": "trash_bin", "x": -9.8, "z": 5.8, "accessory": "bag"},
{"zone": "entrance", "prop": "trash_bin", "x": 9.8, "z": 5.8},
{"zone": "mid_block", "prop": "bench", "x": -10.9, "z": 8.1, "rot": 180.0},
{"zone": "mid_block", "prop": "bench", "x": 10.9, "z": 8.1, "rot": 180.0},
{"zone": "mid_block", "prop": "planter_box", "x": -12.1, "z": 9.5},
{"zone": "mid_block", "prop": "planter_box", "x": 12.1, "z": 9.5},
{"zone": "curb", "prop": "newspaper_box", "x": -11.4, "z": 11.2, "rot": -12.0},
{"zone": "curb", "prop": "newspaper_box", "x": -10.7, "z": 11.2, "rot": -12.0, "accessory": "crate"},
{"zone": "curb", "prop": "fire_hydrant", "x": 10.2, "z": 11.3, "accessory": "cone"},
{"zone": "mid_block", "prop": "palm_tree", "x": -13.2, "z": 9.7, "rot": 10.0},
{"zone": "mid_block", "prop": "palm_tree", "x": 13.2, "z": 9.7, "rot": -10.0},
{"zone": "mid_block", "prop": "round_planter", "x": -12.8, "z": 7.0},
{"zone": "mid_block", "prop": "round_planter", "x": 12.8, "z": 7.0},
{"zone": "curb", "prop": "a_frame_sign", "x": -9.8, "z": 7.0, "rot": 15.0},
{"zone": "curb", "prop": "traffic_cone", "x": 9.6, "z": 10.0},
{"zone": "curb", "prop": "crowd_barrier", "x": 3.0, "z": 10.9},
{"zone": "curb", "prop": "stanchion", "x": -2.6, "z": 10.9},
{"zone": "entrance", "prop": "security_podium", "x": 8.0, "z": 6.1, "rot": -15.0},
],
},
"joke_shop": {
"display_name": "The Joke Shop",
@@ -152,7 +189,10 @@ func _feature(name: String) -> bool:
func build() -> void:
_build_ground()
_build_building()
if _feature("masonic_facade"):
_build_masonic_building()
else:
_build_building()
if _feature("skyline"):
_build_skyline_backdrop()
if _feature("marquee"):
@@ -161,7 +201,15 @@ func build() -> void:
_build_bulb_canopy()
_build_steps()
_build_sidewalk()
if _feature("walk_of_fame"):
_build_walk_of_fame()
if _feature("neighbours"):
_build_neighbour_blocks()
if _feature("opposite_block"):
_build_opposite_block()
_build_street()
if _feature("hollywood_lane_markings"):
_build_hollywood_lane_markings()
if _feature("crosswalk"):
_build_crosswalk()
_build_street_furniture()
@@ -265,6 +313,552 @@ func _build_building() -> void:
## Regular window bands above the entrance. Panes are dark glass by day and
## light up after dark, which gives the facade depth at night.
## Real-world Hollywood Boulevard landmark: the El Capitan Entertainment
## Centre (the 1921 Hollywood Masonic Temple). The facade is a six-column
## Neoclassical portico with a recessed entrance, eight stone steps, a
## crosscut-grille transom, red event banners and the Masonic parapet motto.
## The two neighbours (El Capitan Theatre and the retail block) and the
## opposite Ovation Hollywood frontage give the block its real context.
func _build_masonic_building() -> void:
var building := StaticBody3D.new()
building.name = "Building"
var w := float(spec["facade_width"])
var h := float(spec["facade_height"])
var d := float(spec["facade_depth"])
var cz := float(spec["facade_center_z"])
var front := cz + d * 0.5
var stone := Color(0.86, 0.84, 0.80)
var stone_dark := Color(0.61, 0.59, 0.55)
var stone_light := Color(0.93, 0.91, 0.87)
# Main stone mass. The real building is brick/concrete clad in a light
# limestone-like render; granite/limestone keeps the block consistent with
# the existing PBR surface library.
var facade := _add_box(building, Vector3(w, h, d), Vector3(0, h * 0.5, cz),
stone, false, T_LIMESTONE, Vector2(w * 0.22, h * 0.18), "stone")
facade.name = "MasonicFacade"
# Rusticated base and string courses.
_add_box(building, Vector3(w + 0.3, 1.0, d + 0.3), Vector3(0, 0.5, cz + 0.12),
stone_dark, true, T_LIMESTONE, Vector2(w * 0.22, 0.8), "stone")
for y in [1.12, 1.48, 1.84, 2.20]:
_add_box(building, Vector3(w, 0.055, 0.16), Vector3(0, y, front + 0.03),
stone_light, false, null, Vector2.ONE, "stone")
# Six engaged Ionic columns across the centre of the facade.
var columns := Node3D.new()
columns.name = "IonicColonnade"
building.add_child(columns)
for index in 6:
var x := -12.4 + float(index) * 4.96
_add_masonic_column(columns, x, front)
# Entablature, frieze and decorated parapet.
_add_box(building, Vector3(w + 0.35, 0.5, 0.7), Vector3(0, 11.55, front + 0.22),
stone_light, true, T_LIMESTONE, Vector2(w * 0.18, 0.55), "stone")
_add_box(building, Vector3(w, 1.15, 0.45), Vector3(0, 12.35, front + 0.16),
stone, true, T_LIMESTONE, Vector2(w * 0.18, 0.8), "stone")
_add_box(building, Vector3(w + 0.55, 0.42, 0.9), Vector3(0, 13.16, front + 0.25),
stone_light, true, null, Vector2.ONE, "stone")
_add_box(building, Vector3(w + 0.2, 0.65, 0.45), Vector3(0, 13.70, front + 0.12),
stone, true, T_LIMESTONE, Vector2(w * 0.18, 0.45), "stone")
_add_box(building, Vector3(w + 0.4, 0.16, 0.58), Vector3(0, 14.10, front + 0.16),
stone_light, true, null, Vector2.ONE, "stone")
# A low balustrade above the cornice, matching the reference roofline.
var balustrade := Node3D.new()
balustrade.name = "ParapetBalustrade"
building.add_child(balustrade)
for index in 15:
var x := -14.0 + float(index) * 2.0
_add_box(balustrade, Vector3(0.16, 0.62, 0.18), Vector3(x, 13.96, front + 0.34),
stone_light, true, null, Vector2.ONE, "stone")
_add_box(balustrade, Vector3(w, 0.12, 0.24), Vector3(0, 14.34, front + 0.34),
stone_light, true, null, Vector2.ONE, "stone")
# Two carved medallions flank the Masonic motto.
for x in [-10.5, 10.5]:
var disc := CylinderMesh.new()
disc.top_radius = 0.44
disc.bottom_radius = 0.44
disc.height = 0.12
disc.radial_segments = 20
var medallion := MeshInstance3D.new()
medallion.mesh = disc
medallion.position = Vector3(x, 12.40, front + 0.61)
medallion.rotation_degrees.x = 90.0
medallion.material_override = Materials.limestone(stone_dark, Vector2(0.8, 0.8))
building.add_child(medallion)
var frieze := Label3D.new()
frieze.name = "FriezeInscription"
frieze.text = "FREEMASONRY BUILDS ITS TEMPLES\nAMONG THE NATIONS AND IN THE HEARTS OF MEN"
frieze.position = Vector3(0, 12.45, front + 0.65)
frieze.font_size = 68
frieze.pixel_size = 0.0080
frieze.modulate = Color(0.25, 0.23, 0.21)
frieze.outline_size = 0
frieze.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
frieze.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
building.add_child(frieze)
marquee_label = frieze
# The continuous transom behind the banners: dark crosscut iron grilles.
var transom := Node3D.new()
transom.name = "TransomGrilles"
building.add_child(transom)
for index in 5:
var x := -9.92 + float(index) * 4.96
_add_masonic_lattice(transom, x, 8.55, front + 0.20, 3.85, 2.1)
# Three red event banners in the central bays, like the reference facade.
var banners := Node3D.new()
banners.name = "EventBanners"
building.add_child(banners)
var banner_texts := ["EL", "CAPITAN", "LIVE"]
for index in 3:
var x := -4.96 + float(index) * 4.96
_add_masonic_banner(banners, x, front, banner_texts[index])
# Central recessed double entrance and two street-level side doors.
_add_masonic_door(building, 0.0, 4.2, 5.0, front)
for side in [-1.0, 1.0]:
_add_masonic_door(building, side * 9.92, 2.5, 4.4, front)
# Display cases in the two remaining bays.
for x in [-4.96, 4.96]:
_add_box(building, Vector3(2.2, 3.2, 0.18), Vector3(x, 3.1, front + 0.16),
Color(0.10, 0.10, 0.10), true, null, Vector2.ONE, "painted_metal")
_add_box(building, Vector3(1.7, 2.2, 0.08), Vector3(x, 3.35, front + 0.27),
Color(0.07, 0.08, 0.11), false, null, Vector2.ONE, "glass")
_add_box(building, Vector3(2.4, 0.18, 0.28), Vector3(x, 4.75, front + 0.14),
stone_light, true, null, Vector2.ONE, "stone")
# Door lock indicator beside the central handle.
door_indicator = _add_box(building, Vector3(0.16, 0.16, 0.22),
Vector3(2.55, 3.32, front + 0.30), Color(0.1, 0.9, 0.3))
door_indicator.material_override = _lock_indicator_material(Color(0.1, 0.9, 0.3))
# Warm practical wash for the columns and entrance after dark. The lights
# are off in daylight and set by `set_night_mode`, like the street lamps.
for x in [-10.5, -5.5, 5.5, 10.5]:
var wash := OmniLight3D.new()
wash.name = "FacadeWash_%d" % int(x)
wash.position = Vector3(x, 7.2, front + 2.2)
wash.omni_range = 16.0 if Materials.modern_light_falloff() else 10.0
wash.omni_attenuation = 1.0
wash.light_energy = 0.0
wash.light_color = Color(1.0, 0.82, 0.56)
building.add_child(wash)
_accent_omni.append(wash)
# A second, dimmer row higher up keeps the upper column shafts and capitals
# from disappearing after dark.
for x in [-10.5, -5.5, 5.5, 10.5]:
var high := OmniLight3D.new()
high.name = "UpperWash_%d" % int(x)
high.position = Vector3(x, 10.4, front + 1.8)
high.omni_range = 15.0
high.omni_attenuation = 1.0
high.light_energy = 0.0
high.light_color = Color(1.0, 0.78, 0.50)
building.add_child(high)
_accent_omni.append(high)
# Stone collision volume behind the recessed doors.
var col := BoxShape3D.new()
col.size = Vector3(w, h, d)
var so := building.create_shape_owner(building)
building.shape_owner_add_shape(so, col)
building.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, h * 0.5, cz)))
root.add_child(building)
func _add_masonic_column(parent: Node3D, x: float, front: float) -> void:
var z := front + 0.50
var stone := Color(0.9, 0.88, 0.84)
_add_box(parent, Vector3(1.12, 0.48, 1.08), Vector3(x, 1.24, z),
Color(0.78, 0.76, 0.72), true, T_LIMESTONE, Vector2(0.8, 0.5), "stone")
_add_box(parent, Vector3(0.94, 0.34, 0.94), Vector3(x, 1.65, z),
stone, true, null, Vector2.ONE, "stone")
var shaft := CylinderMesh.new()
shaft.top_radius = 0.34
shaft.bottom_radius = 0.43
shaft.height = 8.9
shaft.radial_segments = 14
shaft.rings = 4
var mesh := MeshInstance3D.new()
mesh.name = "Column_%d" % int(round(x))
mesh.mesh = shaft
mesh.position = Vector3(x, 6.27, z)
mesh.material_override = Materials.limestone(stone, Vector2(1.4, 4.2))
mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
parent.add_child(mesh)
var capital := CylinderMesh.new()
capital.top_radius = 0.54
capital.bottom_radius = 0.40
capital.height = 0.42
capital.radial_segments = 14
var cap := MeshInstance3D.new()
cap.mesh = capital
cap.position = Vector3(x, 10.93, z)
cap.material_override = Materials.limestone(stone, Vector2(1.0, 0.6))
cap.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
parent.add_child(cap)
_add_box(parent, Vector3(1.12, 0.20, 1.10), Vector3(x, 11.22, z),
stone, true, null, Vector2.ONE, "stone")
## Dark crosscut iron grille panel. The bars are deliberately simple boxes: at
## gameplay distance they read as the reference transom without a texture cost.
func _add_masonic_lattice(parent: Node3D, x: float, y: float, z: float,
width: float, height: float) -> void:
_add_box(parent, Vector3(width, height, 0.08), Vector3(x, y, z),
Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "painted_metal")
var columns := 6
for index in columns:
var bar_x := x - width * 0.5 + (float(index) + 0.5) * width / float(columns)
_add_box(parent, Vector3(0.055, height, 0.05), Vector3(bar_x, y, z + 0.07),
Color(0.23, 0.22, 0.20), false, null, Vector2.ONE, "metal")
for index in 4:
var bar_y := y - height * 0.5 + (float(index) + 0.5) * height / 4.0
_add_box(parent, Vector3(width, 0.055, 0.05), Vector3(x, bar_y, z + 0.07),
Color(0.23, 0.22, 0.20), false, null, Vector2.ONE, "metal")
func _add_masonic_banner(parent: Node3D, x: float, front: float, text: String) -> void:
var red := Color(0.58, 0.055, 0.065)
_add_box(parent, Vector3(2.62, 5.55, 0.06), Vector3(x, 8.2, front + 0.73),
red, true, null, Vector2.ONE, "cloth")
for y in [5.55, 10.85]:
_add_box(parent, Vector3(2.82, 0.10, 0.12), Vector3(x, y, front + 0.75),
Color(0.78, 0.62, 0.28), true, null, Vector2.ONE, "metal")
_add_box(parent, Vector3(0.10, 5.55, 0.10), Vector3(x - 1.28, 8.2, front + 0.78),
Color(0.78, 0.62, 0.28), false, null, Vector2.ONE, "metal")
_add_box(parent, Vector3(0.10, 5.55, 0.10), Vector3(x + 1.28, 8.2, front + 0.78),
Color(0.78, 0.62, 0.28), false, null, Vector2.ONE, "metal")
var label := Label3D.new()
label.name = "Banner_%s" % text
label.text = "\n".join(text.split(""))
label.position = Vector3(x, 8.2, front + 0.80)
label.font_size = 112
label.pixel_size = 0.0044
label.modulate = Color(1.0, 0.83, 0.47)
label.outline_size = 0
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
parent.add_child(label)
func _add_masonic_door(parent: Node3D, x: float, width: float, height: float,
front: float) -> void:
var stone_light := Color(0.93, 0.91, 0.87)
var bottom := 1.0
# Recessed stone surround and dark timber/iron door.
_add_box(parent, Vector3(width + 0.55, height + 0.35, 0.18),
Vector3(x, bottom + height * 0.5, front + 0.06),
Color(0.33, 0.31, 0.28), true, null, Vector2.ONE, "stone")
_add_box(parent, Vector3(width, height, 0.14),
Vector3(x, bottom + height * 0.5, front + 0.14),
Color(0.075, 0.065, 0.060), true, null, Vector2.ONE, "wood")
for side in [-1.0, 1.0]:
_add_box(parent, Vector3(0.10, height - 0.25, 0.08),
Vector3(x + side * width * 0.28, bottom + height * 0.5, front + 0.22),
Color(0.33, 0.25, 0.16), false, null, Vector2.ONE, "wood")
_add_box(parent, Vector3(0.055, 0.55, 0.055),
Vector3(x + side * 0.22, bottom + 2.55, front + 0.24),
Color(0.72, 0.65, 0.45), false, null, Vector2.ONE, "metal")
# Lit transom above the door (the reference has a continuous grille band).
var pane := _add_box(parent, Vector3(width * 0.88, 0.72, 0.08),
Vector3(x, bottom + height + 0.38, front + 0.15), Color(1, 1, 1))
var pane_mat := Materials.window_pane()
pane.material_override = pane_mat
_window_mats.append(pane_mat)
for index in 3:
_add_box(parent, Vector3(width * 0.88, 0.045, 0.04),
Vector3(x, bottom + height + 0.10 + float(index) * 0.27, front + 0.20),
Color(0.20, 0.19, 0.18), false, null, Vector2.ONE, "metal")
## Neighbouring El Capitan Theatre (6838) and the retail block east of the
## temple. They are deliberately simpler than the hero facade but preserve the
## street wall, marquee scale and the vertical theatre sign.
func _build_neighbour_blocks() -> void:
var group := Node3D.new()
group.name = "NeighbourBlocks"
root.add_child(group)
# West neighbour: El Capitan Theatre.
var theatre := StaticBody3D.new()
theatre.name = "ElCapitanTheatre"
_add_box(theatre, Vector3(17.6, 18.5, 7.0), Vector3(-24.1, 9.25, -7.5),
Color(0.82, 0.74, 0.61), false, T_LIMESTONE, Vector2(6.0, 8.0), "stone")
_add_box(theatre, Vector3(18.0, 0.34, 7.4), Vector3(-24.1, 6.4, -7.5),
Color(0.63, 0.54, 0.43), true, null, Vector2.ONE, "stone")
_add_box(theatre, Vector3(18.0, 0.48, 7.4), Vector3(-24.1, 18.2, -7.5),
Color(0.56, 0.47, 0.38), true, null, Vector2.ONE, "stone")
for x in [-30.0, -26.8, -23.6, -20.4, -17.2]:
for y in [8.8, 12.6]:
_add_box(theatre, Vector3(2.4, 2.4, 0.10), Vector3(x, y, -3.95),
Color(0.09, 0.10, 0.12), false, null, Vector2.ONE, "glass")
_add_box(theatre, Vector3(2.7, 0.18, 0.18), Vector3(x, y - 1.32, -3.90),
Color(0.72, 0.64, 0.52), true, null, Vector2.ONE, "stone")
# The theatre marquee projects over the sidewalk.
_add_box(theatre, Vector3(10.5, 0.42, 2.3), Vector3(-23.4, 5.65, -3.30),
Color(0.45, 0.035, 0.045), true, null, Vector2.ONE, "painted_metal")
var marquee_trim := Materials.emissive(Color(1.0, 0.70, 0.36), 1.2)
for index in 8:
var bulb := MeshInstance3D.new()
var sphere := SphereMesh.new()
sphere.radius = 0.06
sphere.height = 0.12
bulb.mesh = sphere
bulb.position = Vector3(-28.0 + float(index) * 1.35, 5.40, -2.20)
bulb.material_override = marquee_trim
theatre.add_child(bulb)
if not _marquee_emissive.has(marquee_trim):
_marquee_emissive.append(marquee_trim)
var marquee_glow := OmniLight3D.new()
marquee_glow.name = "MarqueeGlow"
marquee_glow.position = Vector3(-23.4, 5.3, -2.4)
marquee_glow.omni_range = 18.0 if Materials.modern_light_falloff() else 12.0
marquee_glow.omni_attenuation = 1.0
marquee_glow.light_energy = 0.0
marquee_glow.light_color = Color(1.0, 0.70, 0.38)
theatre.add_child(marquee_glow)
_accent_omni.append(marquee_glow)
_add_box(theatre, Vector3(10.5, 0.08, 2.3), Vector3(-23.4, 5.87, -3.30),
Color(0.95, 0.84, 0.58), false, null, Vector2.ONE, "stone")
var theatre_label := Label3D.new()
theatre_label.name = "ElCapitanMarqueeLabel"
theatre_label.text = "EL CAPITAN"
theatre_label.position = Vector3(-23.4, 5.66, -2.12)
theatre_label.font_size = 110
theatre_label.pixel_size = 0.0040
theatre_label.modulate = Color(1.0, 0.78, 0.46)
theatre_label.billboard = BaseMaterial3D.BILLBOARD_DISABLED
theatre.add_child(theatre_label)
# Vertical blade sign at the temple-side corner.
_add_box(theatre, Vector3(0.92, 7.2, 0.26), Vector3(-16.1, 8.9, -3.18),
Color(0.46, 0.035, 0.045), true, null, Vector2.ONE, "painted_metal")
var blade := Label3D.new()
blade.name = "ElCapitanBladeSign"
blade.text = "EL\nCAP\nITAN"
blade.position = Vector3(-16.1, 8.9, -3.02)
blade.font_size = 82
blade.pixel_size = 0.0042
blade.modulate = Color(1.0, 0.88, 0.62)
blade.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
blade.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
theatre.add_child(blade)
var theatre_col := BoxShape3D.new()
theatre_col.size = Vector3(17.6, 18.5, 7.0)
var theatre_owner := theatre.create_shape_owner(theatre)
theatre.shape_owner_add_shape(theatre_owner, theatre_col)
theatre.shape_owner_set_transform(theatre_owner,
Transform3D(Basis(), Vector3(-24.1, 9.25, -7.5)))
group.add_child(theatre)
# East neighbour: the modern retail block beside the temple.
var retail := StaticBody3D.new()
retail.name = "EastRetailBlock"
_add_box(retail, Vector3(16.0, 13.2, 6.2), Vector3(24.6, 6.6, -7.0),
Color(0.76, 0.74, 0.69), false, T_LIMESTONE, Vector2(6.0, 6.0), "stone")
_add_box(retail, Vector3(16.2, 0.36, 6.6), Vector3(24.6, 13.0, -7.0),
Color(0.61, 0.58, 0.52), true, null, Vector2.ONE, "stone")
_add_box(retail, Vector3(15.2, 4.4, 0.12), Vector3(24.6, 2.4, -3.88),
Color(0.08, 0.09, 0.11), false, null, Vector2.ONE, "glass")
for x in [18.2, 21.8, 25.4, 29.0, 31.0]:
_add_box(retail, Vector3(0.18, 4.4, 0.22), Vector3(x, 2.4, -3.82),
Color(0.16, 0.16, 0.17), true, null, Vector2.ONE, "metal")
for x in [18.2, 22.0, 25.8, 29.6]:
for y in [7.4, 10.2]:
_add_box(retail, Vector3(2.0, 1.8, 0.10), Vector3(x, y, -3.92),
Color(0.10, 0.11, 0.13), false, null, Vector2.ONE, "glass")
_add_box(retail, Vector3(2.3, 0.15, 0.17), Vector3(x, y - 1.0, -3.88),
Color(0.66, 0.63, 0.56), true, null, Vector2.ONE, "stone")
var retail_label := Label3D.new()
retail_label.name = "EastRetailSign"
retail_label.text = "ZARA"
retail_label.position = Vector3(24.6, 5.4, -3.74)
retail_label.font_size = 94
retail_label.pixel_size = 0.0044
retail_label.modulate = Color(0.10, 0.10, 0.10)
retail.add_child(retail_label)
var retail_col := BoxShape3D.new()
retail_col.size = Vector3(16.0, 13.2, 6.2)
var retail_owner := retail.create_shape_owner(retail)
retail.shape_owner_add_shape(retail_owner, retail_col)
retail.shape_owner_set_transform(retail_owner,
Transform3D(Basis(), Vector3(24.6, 6.6, -7.0)))
group.add_child(retail)
## A flat, front-facing arch panel used for the Ovation arcade. Both the stone
## surround and the dark opening use the same mesh at different sizes, which
## keeps the arch silhouette readable without a CSG or custom shader.
func _arch_panel_mesh(width: float, height: float, segments: int = 18) -> ArrayMesh:
var radius := width * 0.5
var spring := maxf(0.0, height - radius)
var polygon := PackedVector2Array()
polygon.append(Vector2(-radius, 0.0))
polygon.append(Vector2(radius, 0.0))
polygon.append(Vector2(radius, spring))
for index in range(1, segments):
var angle := PI * float(index) / float(segments)
polygon.append(Vector2(cos(angle) * radius, spring + sin(angle) * radius))
polygon.append(Vector2(-radius, spring))
var triangles := Geometry2D.triangulate_polygon(polygon)
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
for point in polygon:
vertices.append(Vector3(point.x, point.y, 0.0))
normals.append(Vector3(0.0, 0.0, -1.0))
var arrays := []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_NORMAL] = normals
arrays[Mesh.ARRAY_INDEX] = PackedInt32Array(triangles)
var mesh := ArrayMesh.new()
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
return mesh
## The south side of Hollywood Boulevard: the Ovation Hollywood frontage
## across the four-lane street, with its deep retail podium and sign band.
func _build_opposite_block() -> void:
var opposite := Node3D.new()
opposite.name = "OvationHollywood"
root.add_child(opposite)
# South sidewalk, separate from the north walk.
_add_box(opposite, Vector3(96, 0.18, 6.0), Vector3(0, 0.09, 41.0),
Color(0.55, 0.55, 0.54), false, T_CONCRETE, Vector2(28, 2.0), "concrete")
_add_box(opposite, Vector3(98, 0.22, 0.5), Vector3(0, 0.11, 38.1),
Color(0.62, 0.61, 0.58), false, null, Vector2.ONE, "stone")
# Deep retail mass with a shaded arcade at street level.
_add_box(opposite, Vector3(82, 21.0, 12.0), Vector3(0, 10.5, 50.5),
Color(0.79, 0.75, 0.68), false, T_LIMESTONE, Vector2(12, 6), "stone")
# Ground-floor arcade: seven tall arched openings with a stone surround. The
# concept reference shows the Ovation frontage as a deep shaded arcade rather
# than a blank glass strip.
var arch_xs := [-24.0, -16.0, -8.0, 0.0, 8.0, 16.0, 24.0]
for x in arch_xs:
var surround := MeshInstance3D.new()
surround.name = "OvationArchSurround"
surround.mesh = _arch_panel_mesh(6.6, 6.4)
surround.position = Vector3(x, 0.18, 44.28)
surround.material_override = Materials.limestone(Color(0.90, 0.86, 0.78), Vector2(1.2, 1.0))
opposite.add_child(surround)
var opening := MeshInstance3D.new()
opening.name = "OvationArchOpening"
opening.mesh = _arch_panel_mesh(5.5, 5.6)
opening.position = Vector3(x, 0.18, 44.24)
if int((x + 24.0) / 8.0) % 2 == 0:
var shop_pane := Materials.window_pane()
opening.material_override = shop_pane
_window_mats.append(shop_pane)
else:
opening.material_override = Materials.glass(Color(0.08, 0.09, 0.11, 0.94))
opposite.add_child(opening)
# Pilasters between the arcade bays and a string course above them break up
# the long frontage instead of leaving one flat grey wall.
for x in [-28.0, -20.0, -12.0, -4.0, 4.0, 12.0, 20.0, 28.0]:
_add_box(opposite, Vector3(0.9, 6.4, 0.45), Vector3(x, 3.4, 44.22),
Color(0.86, 0.82, 0.74), true, null, Vector2.ONE, "stone")
_add_box(opposite, Vector3(82, 0.35, 0.40), Vector3(0, 6.9, 44.20),
Color(0.84, 0.80, 0.72), true, null, Vector2.ONE, "stone")
_add_box(opposite, Vector3(82, 0.38, 0.90), Vector3(0, 20.82, 44.10),
Color(0.72, 0.68, 0.61), true, null, Vector2.ONE, "stone")
# Upper ribbon windows and the sign band.
for row_index in 3:
var row: float = [9.0, 12.2, 15.4][row_index]
for x in range(-34, 38, 8):
var window := _add_box(opposite, Vector3(5.0, 1.25, 0.12),
Vector3(float(x), row, 44.30), Color(0.09, 0.10, 0.12),
false, null, Vector2.ONE, "glass")
if (row_index + int((x + 34) / 8)) % 3 != 1:
var pane_mat := Materials.window_pane()
window.material_override = pane_mat
_window_mats.append(pane_mat)
_add_box(opposite, Vector3(82, 0.45, 0.70), Vector3(0, 18.0, 44.15),
Color(0.60, 0.56, 0.49), true, null, Vector2.ONE, "stone")
var ovation := Label3D.new()
ovation.name = "OvationSign"
ovation.text = "OVATION HOLLYWOOD"
ovation.position = Vector3(0, 16.4, 44.05)
ovation.font_size = 150
ovation.pixel_size = 0.0052
ovation.modulate = Color(0.16, 0.14, 0.12)
ovation.outline_size = 0
ovation.outline_modulate = Color(0.90, 0.87, 0.80, 0.8)
# The sign faces north across the boulevard, so its front points -Z.
ovation.rotation.y = PI
opposite.add_child(ovation)
_ovation_sign = ovation
# Practical wash across the boulevard so the opposite frontage reads after
# dark instead of becoming a black slab.
for x in [-24.0, 0.0, 24.0]:
var wash := OmniLight3D.new()
wash.name = "OvationWash_%d" % int(x)
wash.position = Vector3(x, 6.0, 40.2)
wash.omni_range = 18.0 if Materials.modern_light_falloff() else 12.0
wash.omni_attenuation = 1.0
wash.light_energy = 0.0
wash.light_color = Color(1.0, 0.84, 0.62)
opposite.add_child(wash)
_accent_omni.append(wash)
# South-side palms frame the far sidewalk.
for side in [-1.0, 1.0]:
var palm := _add_prop_model(opposite, "palm_tree",
Vector3(side * 18.0, 0.18, 41.2), 90.0 if side < 0.0 else -90.0)
palm.name = "SouthPalm_%s" % ("west" if side < 0.0 else "east")
## Hollywood Walk of Fame: dark terrazzo band, brass star discs and coral
## five-point stars along the entrance sidewalk.
func _build_walk_of_fame() -> void:
var walk := Node3D.new()
walk.name = "WalkOfFame"
root.add_child(walk)
_add_box(walk, Vector3(30.0, 0.035, 1.75), Vector3(0, 0.198, 9.15),
Color(0.20, 0.18, 0.17), false, null, Vector2.ONE, "stone")
var star_mesh := _star_mesh(0.40, 0.17)
var brass := Materials.flat(Color(0.70, 0.55, 0.24), "metal")
var coral := Materials.flat(Color(0.72, 0.30, 0.28), "stone")
for index in 10:
var x := -13.5 + float(index) * 3.0
_add_cylinder_mesh(walk, 0.55, 0.02, Vector3(x, 0.214, 9.15),
Color(0.58, 0.49, 0.30), "metal")
var star := MeshInstance3D.new()
star.name = "WalkStar_%d" % index
star.mesh = star_mesh
star.position = Vector3(x, 0.226, 9.15)
star.material_override = coral if index % 2 == 0 else brass
walk.add_child(star)
func _star_mesh(outer: float, inner: float) -> ArrayMesh:
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
var indices := PackedInt32Array()
for index in 10:
var angle := -PI * 0.5 + float(index) * PI / 5.0
var radius := outer if index % 2 == 0 else inner
vertices.append(Vector3(cos(angle) * radius, 0.0, sin(angle) * radius))
normals.append(Vector3.UP)
vertices.append(Vector3.ZERO)
normals.append(Vector3.UP)
var center := 10
for index in 10:
indices.append(center)
indices.append(index)
indices.append((index + 1) % 10)
var arrays := []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_NORMAL] = normals
arrays[Mesh.ARRAY_INDEX] = indices
var mesh := ArrayMesh.new()
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
return mesh
## Four-lane boulevard markings: double-yellow centre, broken white lane lines,
## and the red-painted kerb from the reference photographs.
func _build_hollywood_lane_markings() -> void:
var street_w := float(spec.get("street_width", 120.0))
var street_d := float(spec.get("street_depth", 26.0))
var front := float(spec.get("street_front_z", 12.0))
var center_z := front + street_d * 0.5
var yellow := Color(0.86, 0.70, 0.16)
for offset in [-0.14, 0.14]:
_add_box(root, Vector3(street_w * 0.92, 0.02, 0.09),
Vector3(0, 0.115, center_z + offset), yellow, false, null, Vector2.ONE, "concrete")
var white := Color(0.88, 0.87, 0.80)
for offset in [-6.5, 6.5]:
for x in range(-55, 56, 6):
_add_box(root, Vector3(2.6, 0.02, 0.08),
Vector3(float(x), 0.116, center_z + offset),
white, false, null, Vector2.ONE, "concrete")
# Red kerb paint: the painted face of the concrete kerb.
_add_box(root, Vector3(street_w, 0.13, 0.055),
Vector3(0, 0.19, front - 0.26), Color(0.58, 0.075, 0.055),
false, null, Vector2.ONE, "painted_metal")
func _build_facade_windows(parent: Node3D) -> void:
var columns: Array = spec.get("window_columns", [-17.0, -13.6, -10.2, -6.8, 6.8, 10.2, 13.6, 17.0])
var rows: Array = spec.get("window_rows", [9.2, 12.4])
@@ -302,22 +896,34 @@ func _build_steps() -> void:
var y: float = i * height
var z: float = float(count) * tread - i * tread
var w: float = step_w - i * 0.35
_add_box(steps, Vector3(w, height, tread), Vector3(0, y + height / 2.0, z), Color(0.55, 0.52, 0.5))
var tread_box := _add_box(steps, Vector3(w, height, tread),
Vector3(0, y + height / 2.0, z), Color(0.55, 0.52, 0.5))
if _feature("masonic_facade"):
tread_box.material_override = Materials.limestone(
Color(0.70, 0.67, 0.61), Vector2(w * 0.55, 0.55))
# Add collision shape to each tread
var tread_col := BoxShape3D.new()
tread_col.size = Vector3(w, height, tread)
var owner_id := steps.create_shape_owner(steps)
steps.shape_owner_add_shape(owner_id, tread_col)
steps.shape_owner_set_transform(owner_id, Transform3D(Basis(), Vector3(0, y + height / 2.0, z)))
# Landing at the top with collision
# Landing at the top with collision. The depth/centre are spec-driven so the
# eight-step Masonic entrance reaches the recessed doors.
var landing_y: float = count * height + 0.1
var landing_w := step_w + 1.0
_add_box(steps, Vector3(landing_w, 0.2, 3), Vector3(0, landing_y, -1.5), Color(0.5, 0.47, 0.45))
var landing_depth := float(spec.get("landing_depth", 3.0))
var landing_center_z := float(spec.get("landing_center_z", -1.5))
var landing_box := _add_box(steps, Vector3(landing_w, 0.2, landing_depth),
Vector3(0, landing_y, landing_center_z), Color(0.5, 0.47, 0.45))
if _feature("masonic_facade"):
landing_box.material_override = Materials.limestone(
Color(0.70, 0.67, 0.61), Vector2(landing_w * 0.55, 1.0))
var landing_col := BoxShape3D.new()
landing_col.size = Vector3(landing_w, 0.2, 3)
landing_col.size = Vector3(landing_w, 0.2, landing_depth)
var landing_owner := steps.create_shape_owner(steps)
steps.shape_owner_add_shape(landing_owner, landing_col)
steps.shape_owner_set_transform(landing_owner, Transform3D(Basis(), Vector3(0, landing_y, -1.5)))
steps.shape_owner_set_transform(landing_owner,
Transform3D(Basis(), Vector3(0, landing_y, landing_center_z)))
root.add_child(steps)
func _build_sidewalk() -> void:
@@ -342,15 +948,20 @@ func _build_street() -> void:
# asphalt map (glossier, with lamp reflections) and back.
var street_w := float(spec.get("street_width", 120.0))
var street_d := float(spec.get("street_depth", 26.0))
var curb_z := float(spec.get("sidewalk_depth", 8.0)) + 4.1
var street_z := curb_z + street_d * 0.5 - 3.1
var curb_z := float(spec.get("curb_z", float(spec.get("sidewalk_depth", 8.0)) + 4.1))
# The default venues use the legacy 3.1 m overlap; the Hollywood block has an
# explicit street frontage so the kerb, sidewalk and red paint line up.
var street_front := float(spec.get("street_front_z", curb_z - 3.1))
var street_z := street_front + street_d * 0.5
_street_mat = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(street_w * 0.25, street_d * 0.27))
var street_mesh := _add_box(street, Vector3(street_w, 0.1, street_d), Vector3(0, 0.05, street_z),
Color(0.62, 0.63, 0.66), false, T_ASPHALT, Vector2(street_w * 0.25, street_d * 0.27), "asphalt")
street_mesh.material_override = _street_mat
# Center dashes along the street (x varied so they form a lane line, not a stack).
for i in range(-int(street_w / 3.0), int(street_w / 3.0) + 1, 4):
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, street_z), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
# Legacy centre dashes. The Hollywood block replaces these with the proper
# four-lane markings after the street is built.
if not _feature("hollywood_lane_markings"):
for i in range(-int(street_w / 3.0), int(street_w / 3.0) + 1, 4):
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, street_z), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
# Curb between the sidewalk and the roadway.
_add_box(street, Vector3(street_w, 0.22, 0.5), Vector3(0, 0.11, curb_z), Color(0.62, 0.61, 0.58), false, null, Vector2.ONE, "concrete")
var col := BoxShape3D.new()
@@ -517,6 +1128,9 @@ func _build_library_dressing() -> void:
if _feature("wall_lanterns") and ResourceLoader.exists(BLENDER_PROP_DIR + "wall_lantern.glb"):
if venue_id == "joke_shop":
_add_wall_lantern(Vector3(3.2, 3.3, -3.72), true)
elif _feature("masonic_facade"):
for side in [-13.6, 13.6]:
_add_wall_lantern(Vector3(side, 3.4, -3.62), true)
else:
for side in [-4.9, 4.9]:
_add_wall_lantern(Vector3(side, 3.3, -3.78), false)
@@ -549,7 +1163,7 @@ func _build_bulb_canopy() -> void:
var omni := OmniLight3D.new()
omni.position = Vector3(x, 5.15, front + 1.4)
omni.omni_range = 12.0 if Materials.modern_light_falloff() else 8.0
omni.omni_attenuation = 0.0 if Materials.modern_light_falloff() else 1.0
omni.omni_attenuation = 1.0
omni.light_energy = 0.0
omni.light_color = Color(1.0, 0.72, 0.36)
root.add_child(omni)
@@ -588,7 +1202,7 @@ func _add_wall_lantern(pos: Vector3, with_light: bool) -> void:
omni.name = "LanternGlow"
omni.position = pos + Vector3(0.0, -0.1, 0.45)
omni.omni_range = 14.0 if Materials.modern_light_falloff() else 9.0
omni.omni_attenuation = 0.0 if Materials.modern_light_falloff() else 1.0
omni.omni_attenuation = 1.0
omni.light_energy = 0.0
omni.light_color = Color(1.0, 0.72, 0.38)
root.add_child(omni)
@@ -662,14 +1276,19 @@ func _build_street_furniture() -> void:
# arm points to local -X, so the left side is rotated 180 degrees to face
# the sidewalk.
if _feature("street_lamps"):
for side in [-7.5, 7.5]:
var holder := _add_prop_model(root, "street_lamp",
Vector3(side, 0.18, 8.5), 180.0 if side < 0.0 else 0.0)
holder.name = "Lamp_%d" % int(side)
var lamp_positions: Array = spec.get("street_lamp_positions", [])
if lamp_positions.is_empty():
for side in [-7.5, 7.5]:
lamp_positions.append(Vector3(side, 0.18, 8.5))
for index in lamp_positions.size():
var lamp_pos: Vector3 = lamp_positions[index]
var holder := _add_prop_model(root, "street_lamp", lamp_pos,
180.0 if lamp_pos.x < 0.0 else 0.0)
holder.name = "Lamp_%d" % index
var omni := OmniLight3D.new()
omni.position = Vector3(-0.85, 4.65, 0)
omni.omni_range = 16.0 if Materials.modern_light_falloff() else 12.0
omni.omni_attenuation = 0.0 if Materials.modern_light_falloff() else 1.0
omni.omni_attenuation = 1.0
omni.light_energy = 0.0
omni.light_color = Color(1, 0.93, 0.75)
holder.add_child(omni)
@@ -685,6 +1304,27 @@ func _build_street_furniture() -> void:
_add_prop_model(root, "street_tree", Vector3(x, 0.18, z),
float((i * 37) % 360))
## Total triangles in the whole venue, including procedural architecture. Used by
## the map audit to catch duplicate/hidden geometry after a rebuild.
func world_triangle_count() -> int:
var total := 0
for node in root.find_children("*", "MeshInstance3D", true, false):
var mi := node as MeshInstance3D
if mi == null or mi.mesh == null:
continue
for index in mi.mesh.get_surface_count():
var arrays := mi.mesh.surface_get_arrays(index)
var indices: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
if indices.size() > 0:
total += indices.size() / 3
else:
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
total += vertices.size() / 3
return total
func world_mesh_count() -> int:
return root.find_children("*", "MeshInstance3D", true, false).size()
## Total triangles across the placed Blender library pieces, for the draw budget
## test. Counts indices when present, otherwise the vertex stream.
func library_triangle_count() -> int:
@@ -1034,15 +1674,32 @@ func set_night_mode(night: bool) -> void:
mat.emission_energy_multiplier = (2.0 if night else 0.0)
for omni in _lamp_omni:
# Two lamps at full energy washed the whole sidewalk grey; keep visible
# pools of light instead of flat night-time brightness.
omni.light_energy = (1.8 if night else 0.0)
# pools of light instead of flat night-time brightness. The wall sconces
# sit close to the stone, so they need far less energy than street lamps.
var lamp_energy := 2.2
if omni.name == "LanternGlow":
lamp_energy = 3.0 if venue_id == "joke_shop" else 1.3
omni.light_energy = (lamp_energy if night else 0.0)
for omni in _accent_omni:
# Keep the facade washes soft: the reference night reads as dark stone
# with warm pools at the sconces, not a flooded yellow wall.
var energy := 1.2
if omni.name.begins_with("FacadeWash"):
energy = 0.50
elif omni.name.begins_with("UpperWash"):
energy = 0.28
elif omni.name.begins_with("OvationWash"):
energy = 0.75
omni.light_energy = (energy if night else 0.0)
if _ovation_sign:
_ovation_sign.modulate = Color(0.90, 0.84, 0.70) if night else Color(0.16, 0.14, 0.12)
for mat in _marquee_emissive:
mat.emission_energy_multiplier = (2.0 if night else 0.2)
# Light most, not all, of the windows: a fully lit grid reads as a light box
# instead of an occupied building.
for i in _window_mats.size():
var lit := night and i % 3 != 2
_window_mats[i].emission_energy_multiplier = (1.1 if lit else 0.0)
_window_mats[i].emission_energy_multiplier = (0.65 if lit else 0.0)
if marquee_label:
marquee_label.modulate.a = (1.0 if night else 0.9)