Refine environment surfaces and skyline haze
Full build / Import + headless tests (push) Successful in 42s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-02 20:38:58 +01:00
1 parent d7220c1a0a
commit fb3da52438
8 files changed
+91 -22

No files matched your search

+26 -7
View File
@@ -17,10 +17,14 @@ const T_SKYLINE_NIGHT := preload(PICK_DIR + "skyline_night.png")
## Height of each backdrop plane (metres); the bases sit on the ground plane.
const SKYLINE_HEIGHT := 90.0
const SKYLINE_BY_PHASE := {
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04)},
Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08)},
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0)},
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65)},
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04),
"haze": Color(0.62, 0.71, 0.82), "haze_strength": 0.36},
Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08),
"haze": Color(0.66, 0.75, 0.85), "haze_strength": 0.34},
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0),
"haze": Color(0.55, 0.43, 0.40), "haze_strength": 0.30},
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65),
"haze": Color(0.07, 0.09, 0.14), "haze_strength": 0.22},
}
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
@@ -648,8 +652,10 @@ func _build_sunset_neighbours() -> void:
# Ground-floor shopfront and a red canvas awning.
_add_box(west, Vector3(8.6, 2.4, 0.12), Vector3(-22.6, 1.5, west_front + 0.08),
Color(0.08, 0.10, 0.12), false, null, Vector2.ONE, "glass")
# Keep the cream stripe: tinting the whole atlas dark red collapsed the
# canvas into a flat band at portrait distance.
_add_box(west, Vector3(9.2, 0.22, 1.3), Vector3(-22.6, 3.0, west_front + 0.65),
Color(0.48, 0.10, 0.08), true, T_AWNING, Vector2(3, 0.5), "awning")
Color(0.62, 0.62, 0.62), true, T_AWNING, Vector2(4, 1), "awning")
# Balcony rails on the upper floor.
for x in [-24.8, -21.2, -17.6]:
_add_box(west, Vector3(2.4, 0.08, 0.85), Vector3(x, 5.6, west_front + 0.55),
@@ -1379,8 +1385,10 @@ func _build_walk_of_fame() -> void:
var walk := Node3D.new()
walk.name = "WalkOfFame"
root.add_child(walk)
# Reuse the paver map as a dark terrazzo: a flat colour read as a black
# strip at close range, while the texture supplies the small aggregate.
_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")
Color(0.34, 0.30, 0.27), false, T_CONCRETE, Vector2(18, 1.05), "concrete")
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")
@@ -1595,6 +1603,8 @@ func _build_skyline_backdrop() -> void:
mat.shader = shader
mat.set_shader_parameter("sky_texture", SKYLINE_BY_PHASE[_phase]["texture"])
mat.set_shader_parameter("tint", SKYLINE_BY_PHASE[_phase]["tint"])
mat.set_shader_parameter("haze_color", SKYLINE_BY_PHASE[_phase]["haze"])
mat.set_shader_parameter("haze_strength", SKYLINE_BY_PHASE[_phase]["haze_strength"])
mat.set_shader_parameter("repeats", 2.0)
_skyline_mat = mat
var mesh := PlaneMesh.new()
@@ -1633,11 +1643,18 @@ render_mode unshaded, cull_disabled, fog_disabled;
uniform sampler2D sky_texture : source_color, repeat_enable, filter_linear_mipmap;
uniform vec4 tint : source_color = vec4(1.0);
uniform vec4 haze_color : source_color = vec4(0.62, 0.71, 0.82, 1.0);
uniform float haze_strength = 0.36;
uniform float repeats = 2.0;
void fragment() {
vec4 panorama = texture(sky_texture, vec2(UV.x * repeats, UV.y));
ALBEDO = panorama.rgb * tint.rgb;
// Atmospheric perspective: blend the lower part of the panorama toward
// the horizon colour so the towers sit in the distance instead of reading
// as a crisp cardboard cut-out.
float world_y = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).y;
float horizon = clamp(1.0 - world_y / 42.0, 0.0, 1.0);
ALBEDO = mix(panorama.rgb * tint.rgb, haze_color.rgb, horizon * haze_strength);
ALPHA = panorama.a * tint.a;
ALPHA_SCISSOR_THRESHOLD = 0.5;
}
@@ -2241,6 +2258,8 @@ func set_phase(phase: int) -> void:
if _skyline_mat != null:
_skyline_mat.set_shader_parameter("sky_texture", entry["texture"])
_skyline_mat.set_shader_parameter("tint", entry["tint"])
_skyline_mat.set_shader_parameter("haze_color", entry["haze"])
_skyline_mat.set_shader_parameter("haze_strength", entry["haze_strength"])
var night: bool = phase == Rules.Phase.EVENING or phase == Rules.Phase.LATE
set_night_mode(night)
if _street_mat != null:
+15
View File
@@ -62,6 +62,18 @@ const SHOTS_JOKE_DAY := [
const SHOTS_STUDIO := [
{"name": "studio_door", "pos": Vector3(0, 0.1, 6.2), "yaw": 0.0, "pitch": 2.0},
]
## Surface close-ups for the 0.9.5 texture audit; HUD is hidden for this stage.
const SHOTS_SURFACES := [
{"name": "surface_pavers", "pos": Vector3(0, 0.1, 10.5), "yaw": 0.0, "pitch": -55.0},
{"name": "surface_asphalt", "pos": Vector3(0, 0.1, 14.0), "yaw": 180.0, "pitch": -42.0},
{"name": "surface_limestone", "pos": Vector3(0, 0.1, 2.0), "yaw": 0.0, "pitch": 8.0},
{"name": "surface_facade_wide", "pos": Vector3(-2.0, 0.1, 13.2), "yaw": 12.0, "pitch": 20.0},
]
const SHOTS_SURFACES_JOKE := [
{"name": "surface_awning", "pos": Vector3(-22.6, 0.1, 2.6), "yaw": 0.0, "pitch": 14.0},
{"name": "surface_canopy", "pos": Vector3(0, 0.1, 7.5), "yaw": 0.0, "pitch": 24.0},
{"name": "surface_pavers_joke", "pos": Vector3(0, 0.1, 10.0), "yaw": 0.0, "pitch": -55.0},
]
## A face-on portrait: staged relative to a live NPC by `_stage_portrait()`.
const SHOTS_PORTRAIT := [
{"name": "npc_portrait", "pos": Vector3.ZERO, "yaw": 0.0, "pitch": 0.0,
@@ -70,6 +82,8 @@ const SHOTS_PORTRAIT := [
## Stage list: shift time selects the phase, `venue` switches the scenario first.
const STAGES := [
{"name": "day", "elapsed": 0.0, "venue": "", "shots": SHOTS_DAY, "night": false},
{"name": "surfaces", "elapsed": 0.0, "venue": "", "shots": SHOTS_SURFACES, "night": false},
{"name": "surfaces_joke", "elapsed": 0.0, "venue": "joke_shop", "shots": SHOTS_SURFACES_JOKE, "night": false},
# Portrait next, while the day crowd is still on the street.
{"name": "portrait", "elapsed": 0.0, "venue": "", "shots": SHOTS_PORTRAIT,
"night": false},
@@ -182,6 +196,7 @@ func _begin_stage() -> void:
gm.hud.clear_log()
gm.hud.set_target_label("")
gm.hud.set_action_hint("")
gm.hud.visible = not str(stage["name"]).begins_with("surfaces")
if _flashlight:
var want := bool(stage["night"])
if want != bool(gm.flashlight_on):
+13 -4
View File
@@ -113,8 +113,13 @@ func test_hollywood_uses_the_real_landmark_layout() -> bool:
var banners := root.find_child("EventBanners", true, false)
var d := TestUtils.check(banners != null and banners.get_child_count() >= 9,
"three red event banners with their trim are present")
var e := TestUtils.check(root.find_child("WalkOfFame", true, false) != null,
"the Hollywood Walk of Fame strip is built")
var walk := root.find_child("WalkOfFame", true, false)
var walk_band := walk.get_child(0) as MeshInstance3D if walk != null else null
var walk_material: Material = walk_band.mesh.surface_get_material(0) if walk_band != null else null
var e := TestUtils.check(walk != null and walk_material != null
and walk_material is StandardMaterial3D
and (walk_material as StandardMaterial3D).albedo_texture != null,
"the Hollywood Walk of Fame strip is built with the terrazzo texture")
var f := TestUtils.check(root.get_node_or_null("NeighbourBlocks/ElCapitanTheatre") != null
and root.get_node_or_null("NeighbourBlocks/EastRetailBlock") != null,
"the El Capitan Theatre and east retail neighbours are built")
@@ -260,8 +265,12 @@ func test_skyline_backdrop_uses_alpha() -> bool:
var mat: ShaderMaterial = builder._skyline_mat
var b := TestUtils.check(mat != null and mat.shader != null, "skyline shader material captured")
var code := mat.shader.code if mat != null and mat.shader != null else ""
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled"),
"backdrop shader is alpha-scissored and fog-free")
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled")
and code.contains("haze_strength"),
"backdrop shader is alpha-scissored, fog-free and haze-blended")
var haze := float(mat.get_shader_parameter("haze_strength")) if mat != null else 0.0
var c2 := TestUtils.check(haze > 0.0, "skyline haze is enabled")
c = c and c2
var day: Texture2D = mat.get_shader_parameter("sky_texture")
builder.set_phase(Rules.Phase.LATE)
var night: Texture2D = mat.get_shader_parameter("sky_texture")