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

+36 -6
View File
@@ -45,9 +45,14 @@ var body_camera_on := false
var coordinator: ShiftCoordinator = null
var guard: GuardController = null
var _sun: DirectionalLight3D = null
var _fill: DirectionalLight3D = null
var _env_node: WorldEnvironment = null
var _sky: ProceduralSkyMaterial = null
var _last_phase: int = -1
## Menu-only alias for the night scenery variant; it resolves to the Hollywood
## scenario with a LATE phase offset.
const HOLLYWOOD_NIGHT_ID := "hollywood_building_night"
var _start_phase_override: int = -1
## Per-phase lighting looks for the sun, sky, and ambient (PLAN.md: shift
## structure — phase-based lighting). Night phases switch street lamps on.
@@ -58,7 +63,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.45, 0.5, 0.55), "ambient": Color(0.75, 0.78, 0.85), "night": false,
"exposure": 0.6, "glow": 0.45, "fog_color": Color(0.87, 0.83, 0.76),
"fog_density": 0.0034, "fog_energy": 0.5, "contrast": 1.05, "saturation": 1.04,
"ambient_energy": 0.1, "volumetric_density": 0.004, "volumetric_energy": 0.0,
"ambient_energy": 0.1, "fill_energy": 0.22, "volumetric_density": 0.004, "volumetric_energy": 0.0,
},
Rules.Phase.MIDDAY: {
"sun_energy": 1.0, "sun_angle": -62.0, "sun_yaw": -30.0, "sun_color": Color(1.0, 0.99, 0.96),
@@ -66,7 +71,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.5, 0.54, 0.58), "ambient": Color(0.85, 0.88, 0.95), "night": false,
"exposure": 0.6, "glow": 0.4, "fog_color": Color(0.88, 0.87, 0.83),
"fog_density": 0.0028, "fog_energy": 0.5, "contrast": 1.06, "saturation": 1.04,
"ambient_energy": 0.28, "volumetric_density": 0.003, "volumetric_energy": 0.0,
"ambient_energy": 0.28, "fill_energy": 0.18, "volumetric_density": 0.003, "volumetric_energy": 0.0,
},
# Evening is dusk, not a bright sunset: the sun is almost down and the street
# is carried by the marquee, the lamps and the lit windows (realism pass).
@@ -76,7 +81,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.1, 0.09, 0.12), "ambient": Color(0.2, 0.19, 0.26), "night": true,
"exposure": 0.72, "glow": 0.8, "fog_color": Color(0.34, 0.27, 0.3),
"fog_density": 0.0026, "fog_energy": 0.45, "contrast": 1.1, "saturation": 1.05,
"ambient_energy": 0.12, "volumetric_density": 0.014, "volumetric_energy": 0.10,
"ambient_energy": 0.12, "fill_energy": 0.10, "volumetric_density": 0.014, "volumetric_energy": 0.10,
},
Rules.Phase.LATE: {
"sun_energy": 0.02, "sun_angle": -8.0, "sun_color": Color(0.3, 0.35, 0.5),
@@ -84,7 +89,7 @@ const PHASE_LIGHTING := {
"sky_ground": Color(0.03, 0.03, 0.05), "ambient": Color(0.06, 0.07, 0.12), "night": true,
"exposure": 0.95, "glow": 1.15, "fog_color": Color(0.08, 0.1, 0.2),
"fog_density": 0.0028, "fog_energy": 0.45, "contrast": 1.12, "saturation": 1.0,
"ambient_energy": 0.22, "volumetric_density": 0.020, "volumetric_energy": 0.14,
"ambient_energy": 0.30, "fill_energy": 0.06, "volumetric_density": 0.020, "volumetric_energy": 0.14,
},
}
var hud: Hud = null
@@ -222,7 +227,7 @@ func _build_guard() -> GuardController:
flashlight.spot_range = 30.0 if Materials.modern_light_falloff() else 20.0
flashlight.spot_angle = 14.0
flashlight.light_energy = 4.2
flashlight.spot_attenuation = 0.0 if Materials.modern_light_falloff() else 0.9
flashlight.spot_attenuation = 1.0
flashlight.spot_angle_attenuation = 0.9
# The volumetric shaft is a Forward+ (desktop HQ) effect. Compatibility and
# headless builds leave the light non-volumetric, so their look is unchanged.
@@ -265,6 +270,7 @@ func _build_scenario(id: String) -> VenueScenario:
return ScenarioFactory.hollywood_building()
func switch_scenario(id: String) -> void:
_start_phase_override = -1
active_scenario_id = id
selected_scenario = _build_scenario(id)
_rebuild_world_for_venue(id)
@@ -315,6 +321,9 @@ func _reset_shift_state() -> void:
hud.set_body_camera(false)
scenario_duration_s = selected_scenario.shift_duration_s
coordinator.setup(selected_scenario, scenario_duration_s, 4242)
if _start_phase_override >= 0:
coordinator.phase_offset_s = _phase_start_s(_start_phase_override)
coordinator.current_phase = coordinator._phase_for_time(0.0)
coordinator.scheduler.spawn_rate_multiplier = float(_difficulty()["spawn"])
_connect_radio()
_connect_crowd()
@@ -336,6 +345,11 @@ func _start_shift() -> void:
# opens and closes.
_refresh_freeze()
func _phase_start_s(phase: int) -> float:
if selected_scenario == null:
return 0.0
return float((selected_scenario.shift_phases.get(phase, {}) as Dictionary).get("start_s", 0.0))
func _show_start_menu() -> void:
_shift_running = false
if pause_overlay:
@@ -353,6 +367,10 @@ func _on_venue_selected(scenario_id: String) -> void:
start_menu.close()
if not _resuming and Save:
Save.clear_shift_state()
_start_phase_override = -1
if scenario_id == HOLLYWOOD_NIGHT_ID:
_start_phase_override = Rules.Phase.LATE
scenario_id = "hollywood_building"
active_scenario_id = scenario_id
_venue_index = maxi(0, VENUE_ORDER.find(scenario_id))
selected_scenario = _build_scenario(scenario_id)
@@ -715,6 +733,15 @@ func _ensure_lighting() -> void:
_sun.directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
_sun.directional_shadow_max_distance = 60.0
scene_root.add_child(_sun)
# A soft sky-bounce fill from the north keeps the shaded Ovation frontage
# readable without adding a second set of hard shadows.
_fill = DirectionalLight3D.new()
_fill.name = "SkyFill"
_fill.rotation_degrees = Vector3(-24, 180, 0)
_fill.light_energy = 0.2
_fill.light_color = Color(0.62, 0.72, 0.9)
_fill.shadow_enabled = false
scene_root.add_child(_fill)
_env_node = WorldEnvironment.new()
var env := Environment.new()
env.background_mode = Environment.BG_SKY
@@ -741,6 +768,8 @@ func _apply_phase_lighting() -> void:
# entrance view reads as shade when the sun is off the facade.
_sun.rotation_degrees = Vector3(float(cfg.get("sun_angle", -50.0)),
float(cfg.get("sun_yaw", -30.0)), 0)
if _fill:
_fill.light_energy = float(cfg.get("fill_energy", 0.16))
if _sky:
_sky.sky_top_color = cfg.get("sky_top", Color.BLUE)
_sky.sky_horizon_color = cfg.get("sky_horizon", Color.WHITE)
@@ -1981,7 +2010,8 @@ func _physics_process(delta: float) -> void:
hud.log("Objective: %s — keep the entrance clear and verify access." % str(
Rules.PHASE_NAMES.get(coordinator.current_phase, "Early")))
hud.set_shift_state(coordinator, selected_scenario)
var preview := coordinator.scheduler.preview(coordinator.elapsed_s, coordinator.current_phase)
var preview := coordinator.scheduler.preview(coordinator.schedule_time_s(),
coordinator.current_phase)
hud.refresh_debug(coordinator, npcs, preview)
_perception_pass()