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

+3
View File
@@ -13,6 +13,9 @@ Semantic Versioning.
- NPC fabric maps now repeat four times across the body and skip the baked - NPC fabric maps now repeat four times across the body and skip the baked
per-character AO, removing the large blotchy patches from shirts, jackets and per-character AO, removing the large blotchy patches from shirts, jackets and
trousers at portrait distance. trousers at portrait distance.
- The Walk of Fame band now uses the paver/terrazzo texture instead of a flat
dark strip, the neighbour awning keeps its cream stripe at close range, and the
distant skyline fades into horizon haze instead of reading as a crisp cut-out.
## [0.9.4] — 2026-11-02 ## [0.9.4] — 2026-11-02
+7
View File
@@ -71,6 +71,13 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
albedo/normal/ORM maps instead of 128² (AI shirt/trousers are resized to 256). The base and all albedo/normal/ORM maps instead of 128² (AI shirt/trousers are resized to 256). The base and all
eight library GLBs plus AO maps were rebuilt; texture files total ~0.5 MB, eight GLBs ~5.75 MB, eight library GLBs plus AO maps were rebuilt; texture files total ~0.5 MB, eight GLBs ~5.75 MB,
and the 24-NPC smoke remains 6.90 ms. and the 24-NPC smoke remains 6.90 ms.
- **Environment surface audit (0.9.5, 2026-11-02):** `capture_realism.gd` gained
`surfaces` (Hollywood pavers/asphalt/limestone/facade) and `surfaces_joke`
(neighbour awning, canopy, pavers) stages with the HUD hidden. The Walk of Fame
band now uses `T_CONCRETE` as dark terrazzo; the neighbour awning uses a neutral
tint (`0.62`) and 4× UV so the red/cream stripes survive; the skyline shader
fades its lower 42 m toward a per-phase haze colour. Brick/asphalt picks needed
no replacement. No seams or broken UV scales found.
- **NPC fabric UV/AO (0.9.5, 2026-11-02):** cloth/pants use - **NPC fabric UV/AO (0.9.5, 2026-11-02):** cloth/pants use
`Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`, and `NpcAvatar` skips the baked per-character `Materials.NPC_FABRIC_UV_SCALE = Vector3(4,4,1)`, and `NpcAvatar` skips the baked per-character
AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a AO on those two slots. The 256² AO was magnifying into large blotches; the fabric now reads as a
+16 -9
View File
@@ -57,16 +57,22 @@ Compatibility/Web parity, deterministic assets and offline CI.
### Environment textures and materials ### Environment textures and materials
- [ ] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas, - [x] Audit the v2 picks (brick, sidewalk pavers, dry/wet asphalt, awning canvas,
day/dusk/night skylines) at close range and in the entrance/street shots for day/dusk/night skylines) at close range and in the entrance/street shots for
seams, tiling, scale, contrast and normal-map strength. seams, tiling, scale, contrast and normal-map strength. (New `surfaces` and
- [ ] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response `surfaces_joke` capture stages; no seam or UV-scale failures found.)
- [x] Refine brick mortar/wear, paver joints/stains, asphalt aggregate/wet response
and awning weave using the existing image/asset tooling; never overwrite a and awning weave using the existing image/asset tooling; never overwrite a
pick, use `-vN` candidates and promote validated results. pick, use `-vN` candidates and promote validated results. (The Walk of Fame
- [ ] Check UV scales and material roughness/metallic profiles; keep day/night band now uses the paver/terrazzo texture; the neighbour awning keeps its
phase behavior and the wet-street transition intact. cream stripe via a neutral tint and 4× UV. Brick/asphalt picks held up.)
- [ ] Review distant skyline haze and foliage/planter surfaces against the concept - [x] Check UV scales and material roughness/metallic profiles; keep day/night
references; fix obvious flatness without regressing readability. phase behavior and the wet-street transition intact. (Profiles and UV
scales passed; dry/wet asphalt and phase switching unchanged.)
- [x] Review distant skyline haze and foliage/planter surfaces against the concept
references; fix obvious flatness without regressing readability. (Added
world-height haze fade to the skyline shader; foliage/planters stayed
stylized and readable, so no mesh change.)
### Character and prop textures ### Character and prop textures
@@ -74,7 +80,8 @@ Compatibility/Web parity, deterministic assets and offline CI.
refine fabric weave, skin variation and fixed detail materials. (256² maps rebuilt; fabric refine fabric weave, skin variation and fixed detail materials. (256² maps rebuilt; fabric
now uses a 4× UV weave and skips baked per-character AO, removing the portrait blotches; now uses a 4× UV weave and skips baked per-character AO, removing the portrait blotches;
skin/hair/gear keep AO. Comparison `sheet_npc_baseline_compare-texuv.jpg`.) skin/hair/gear keep AO. Comparison `sheet_npc_baseline_compare-texuv.jpg`.)
- [ ] Re-bake AO where geometry changes; verify the 256² maps and material wiring. - [x] Re-bake AO where geometry changes; verify the 256² maps and material wiring.
(No character geometry changed in the texture pass; asset/base audits pass.)
- [ ] Audit library props and UI/card/icon textures at their actual screen sizes; - [ ] Audit library props and UI/card/icon textures at their actual screen sizes;
remove blurry/oversized or unused textures where safe. remove blurry/oversized or unused textures where safe.
- [ ] Run texture audits, import, and the full asset test suite; record texture memory - [ ] Run texture audits, import, and the full asset test suite; record texture memory
+10 -2
View File
@@ -235,11 +235,19 @@ godot --path . --script res://src/test/capture_npc_baseline.gd -- \
python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
``` ```
`capture_realism.gd` runs seven stages (day, portrait, evening, late, Joke Shop day, Joke Shop night, studio); `capture_realism.gd` runs nine stages (day, Hollywood surface close-ups, Joke Shop surface close-ups, portrait, evening, late, Joke Shop day, Joke Shop night, studio);
`--stages=day,late` limits it while tuning; `--flashlight` turns on the guard lamp for `--stages=day,late` limits it while tuning; `--flashlight` turns on the guard lamp for
night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one NPC on open sidewalk night stages (used by the Desktop HQ A/B). The `portrait` shot freezes one NPC on open sidewalk
and frames the face, which is the only capture that can judge avatar detail and frames the face, which is the only capture that can judge avatar detail
(`--portrait-type=N` picks a role). The generated realism runs under (`--portrait-type=N` picks a role). For the 0.9.5 texture audit, run the surface
stages directly; the HUD is hidden for them:
```bash
godot --path . --script res://src/test/capture_realism.gd \
-- --out=res://tmp/surfaces --stages=surfaces,surfaces_joke
```
The generated realism runs under
`docs/screens/realism/` are **local, gitignored QA artifacts**; commit only the `docs/screens/realism/` are **local, gitignored QA artifacts**; commit only the
resulting decisions/docs, not the run folders. The texture-adoption run compared resulting decisions/docs, not the run folders. The texture-adoption run compared
the pre-adoption captures with the current build: on the shots both sets captured, the pre-adoption captures with the current build: on the shots both sets captured,
+1
View File
@@ -43,6 +43,7 @@ Implemented so far:
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`. - NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`. - NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
- Release v0.9.4 (2026-11-02): the tagged commit `a57b86c` passed the Gitea checks and full build and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4>. - Release v0.9.4 (2026-11-02): the tagged commit `a57b86c` passed the Gitea checks and full build and published seven artifacts (Linux, Windows, macOS, NSIS setup, AppImage, Web and Desktop HQ). Release page: <https://git.allucanget.biz/zwitschi/security-game/releases/tag/v0.9.4>.
- Environment texture pass (0.9.5 work): `capture_realism.gd` now has hidden-HUD `surfaces`/`surfaces_joke` close-up stages. The Walk of Fame band uses the paver texture as dark terrazzo, the neighbour awning keeps its cream stripe, and the skyline shader blends its lower 42 m toward a per-phase haze colour. Brick, asphalt, limestone and paver UV scales/profiles passed without replacement.
- NPC texture pass (0.9.5 work): the NPC base and all eight library characters now use 256² albedo/normal/ORM maps instead of 128². Cloth and pants use `NPC_FABRIC_UV_SCALE` (4× weave) and skip the baked per-character AO, removing the large blotchy patches in portraits; skin, hair and gear keep baked AO. The base and eight GLBs plus AO maps were rebuilt; the 24-NPC smoke remains green. - NPC texture pass (0.9.5 work): the NPC base and all eight library characters now use 256² albedo/normal/ORM maps instead of 128². Cloth and pants use `NPC_FABRIC_UV_SCALE` (4× weave) and skip the baked per-character AO, removing the large blotchy patches in portraits; skin, hair and gear keep baked AO. The base and eight GLBs plus AO maps were rebuilt; the 24-NPC smoke remains green.
- NPC visual optimization (0.9.4, complete 2026-11-02): all eight variants passed the portrait/readability, proportion, gear, foot/shoe, headgear and final-context gates. The library uses an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000; `npc_base` fallback remains 6,000/4,000); current characters measure 4,482–5,474 triangles and 4,260–5,494 vertices. The 48-frame baseline and 96-frame final-context review pass, the hand/gear offset and shoes are in, and the accepted stylized limits are documented in `npc-visual-review.md`. - NPC visual optimization (0.9.4, complete 2026-11-02): all eight variants passed the portrait/readability, proportion, gear, foot/shoe, headgear and final-context gates. The library uses an 8,000-triangle / 8,000-glTF-vertex cap (up from 6,000/5,000; `npc_base` fallback remains 6,000/4,000); current characters measure 4,482–5,474 triangles and 4,260–5,494 vertices. The 48-frame baseline and 96-frame final-context review pass, the hand/gear offset and shoes are in, and the accepted stylized limits are documented in `npc-visual-review.md`.
- NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`. - NPC face-detail pass (0.9.4 work): ala wings and a smaller nose bridge/tip, one rounded mouth slot, and a darker ear concha; all eight rebuilt. The comparison is `sheet_npc_baseline_compare-face-detail.jpg`.
+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. ## Height of each backdrop plane (metres); the bases sit on the ground plane.
const SKYLINE_HEIGHT := 90.0 const SKYLINE_HEIGHT := 90.0
const SKYLINE_BY_PHASE := { const SKYLINE_BY_PHASE := {
Rules.Phase.EARLY: {"texture": T_SKYLINE_DAY, "tint": Color(0.98, 1.0, 1.04)}, 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)}, "haze": Color(0.62, 0.71, 0.82), "haze_strength": 0.36},
Rules.Phase.EVENING: {"texture": T_SKYLINE_DUSK, "tint": Color(0.9, 0.92, 1.0)}, Rules.Phase.MIDDAY: {"texture": T_SKYLINE_DAY, "tint": Color(1.02, 1.04, 1.08),
Rules.Phase.LATE: {"texture": T_SKYLINE_NIGHT, "tint": Color(1.5, 1.55, 1.65)}, "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. ## steps, building facade, restricted door, sidewalk, pavement, and street edge.
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street ## 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. # 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), _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") 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), _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. # Balcony rails on the upper floor.
for x in [-24.8, -21.2, -17.6]: 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), _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() var walk := Node3D.new()
walk.name = "WalkOfFame" walk.name = "WalkOfFame"
root.add_child(walk) 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), _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 star_mesh := _star_mesh(0.40, 0.17)
var brass := Materials.flat(Color(0.70, 0.55, 0.24), "metal") var brass := Materials.flat(Color(0.70, 0.55, 0.24), "metal")
var coral := Materials.flat(Color(0.72, 0.30, 0.28), "stone") 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.shader = shader
mat.set_shader_parameter("sky_texture", SKYLINE_BY_PHASE[_phase]["texture"]) 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("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) mat.set_shader_parameter("repeats", 2.0)
_skyline_mat = mat _skyline_mat = mat
var mesh := PlaneMesh.new() 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 sampler2D sky_texture : source_color, repeat_enable, filter_linear_mipmap;
uniform vec4 tint : source_color = vec4(1.0); 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; uniform float repeats = 2.0;
void fragment() { void fragment() {
vec4 panorama = texture(sky_texture, vec2(UV.x * repeats, UV.y)); 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 = panorama.a * tint.a;
ALPHA_SCISSOR_THRESHOLD = 0.5; ALPHA_SCISSOR_THRESHOLD = 0.5;
} }
@@ -2241,6 +2258,8 @@ func set_phase(phase: int) -> void:
if _skyline_mat != null: if _skyline_mat != null:
_skyline_mat.set_shader_parameter("sky_texture", entry["texture"]) _skyline_mat.set_shader_parameter("sky_texture", entry["texture"])
_skyline_mat.set_shader_parameter("tint", entry["tint"]) _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 var night: bool = phase == Rules.Phase.EVENING or phase == Rules.Phase.LATE
set_night_mode(night) set_night_mode(night)
if _street_mat != null: if _street_mat != null:
+15
View File
@@ -62,6 +62,18 @@ const SHOTS_JOKE_DAY := [
const SHOTS_STUDIO := [ const SHOTS_STUDIO := [
{"name": "studio_door", "pos": Vector3(0, 0.1, 6.2), "yaw": 0.0, "pitch": 2.0}, {"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()`. ## A face-on portrait: staged relative to a live NPC by `_stage_portrait()`.
const SHOTS_PORTRAIT := [ const SHOTS_PORTRAIT := [
{"name": "npc_portrait", "pos": Vector3.ZERO, "yaw": 0.0, "pitch": 0.0, {"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. ## Stage list: shift time selects the phase, `venue` switches the scenario first.
const STAGES := [ const STAGES := [
{"name": "day", "elapsed": 0.0, "venue": "", "shots": SHOTS_DAY, "night": false}, {"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. # Portrait next, while the day crowd is still on the street.
{"name": "portrait", "elapsed": 0.0, "venue": "", "shots": SHOTS_PORTRAIT, {"name": "portrait", "elapsed": 0.0, "venue": "", "shots": SHOTS_PORTRAIT,
"night": false}, "night": false},
@@ -182,6 +196,7 @@ func _begin_stage() -> void:
gm.hud.clear_log() gm.hud.clear_log()
gm.hud.set_target_label("") gm.hud.set_target_label("")
gm.hud.set_action_hint("") gm.hud.set_action_hint("")
gm.hud.visible = not str(stage["name"]).begins_with("surfaces")
if _flashlight: if _flashlight:
var want := bool(stage["night"]) var want := bool(stage["night"])
if want != bool(gm.flashlight_on): 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 banners := root.find_child("EventBanners", true, false)
var d := TestUtils.check(banners != null and banners.get_child_count() >= 9, var d := TestUtils.check(banners != null and banners.get_child_count() >= 9,
"three red event banners with their trim are present") "three red event banners with their trim are present")
var e := TestUtils.check(root.find_child("WalkOfFame", true, false) != null, var walk := root.find_child("WalkOfFame", true, false)
"the Hollywood Walk of Fame strip is built") 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 var f := TestUtils.check(root.get_node_or_null("NeighbourBlocks/ElCapitanTheatre") != null
and root.get_node_or_null("NeighbourBlocks/EastRetailBlock") != null, and root.get_node_or_null("NeighbourBlocks/EastRetailBlock") != null,
"the El Capitan Theatre and east retail neighbours are built") "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 mat: ShaderMaterial = builder._skyline_mat
var b := TestUtils.check(mat != null and mat.shader != null, "skyline shader material captured") 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 code := mat.shader.code if mat != null and mat.shader != null else ""
var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled"), var c := TestUtils.check(code.contains("ALPHA") and code.contains("fog_disabled")
"backdrop shader is alpha-scissored and fog-free") 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") var day: Texture2D = mat.get_shader_parameter("sky_texture")
builder.set_phase(Rules.Phase.LATE) builder.set_phase(Rules.Phase.LATE)
var night: Texture2D = mat.get_shader_parameter("sky_texture") var night: Texture2D = mat.get_shader_parameter("sky_texture")