Adopt the realism pass and refresh screenshots/docs
Full build / Import + headless tests (push) Successful in 34s
Full build / Full desktop build + optional release (push) Skipped

- add the v2 runtime picks: per-phase skyline panoramas, brick, sidewalk
  pavers, dry/wet asphalt, awning canvas, and their normal maps
- add the realism tooling and evidence: tools/scene_concepts.py, capture_realism.gd,
  concept frames, reports/sheets, and tests
- retune phase lighting/fog/exposure, add sun yaw, brighten the night glow,
  add the flashlight beam mesh, and rebuild the planter/tree assets
- regenerate all eleven staged screenshots from the current build; compare is
  11 good / 0 bad
- finish the visual-alignment closeout in DONE/TODO, update architecture,
  blender-assets, PLAN, and ignore rules (editable Blender sources untracked)
- keep the judge retry and document the verified default judge model

Verification: headless 227/227; Python tooling 86/86; check_screenshots 11/11.
This commit is contained in:
zwitschi committed 2026-11-01 17:03:05 +01:00
1 parent 26ecb93fd4
commit d7e523f63c
70 files changed
+4042 -101

No files matched your search

+101 -24
View File
@@ -2,11 +2,25 @@ class_name WorldBuilder
## Procedurally assembles a minimal Hollywood Boulevard entrance prototype:
const Materials := preload("res://src/game/materials.gd")
const T_BRICK := preload("res://assets/textures/ai/game_ready/brick_facade.png")
const T_CONCRETE := preload("res://assets/textures/ai/game_ready/concrete.png")
const T_ASPHALT := preload("res://assets/textures/ai/game_ready/asphalt.png")
const T_AWNING := preload("res://assets/textures/ai/game_ready/awning.png")
const T_SKYLINE := preload("res://assets/textures/skyline.png")
const Rules := preload("res://src/core/rules.gd")
const PICK_DIR := "res://assets/textures/ai/game_ready/v2/picks/"
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")
## 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")
const T_SKYLINE_DUSK := preload(PICK_DIR + "skyline_dusk.png")
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)},
}
## steps, building facade, restricted door, sidewalk, pavement, and street edge.
## Surfaces use the shared PBR profiles in `src/game/materials.gd`; street
## furniture comes from the generated GLB props (tools/model_pipeline.py) with a
@@ -98,9 +112,18 @@ func _build_building() -> void:
var building := StaticBody3D.new()
building.name = "Building"
# Main facade (textured brick)
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.80, 0.76, 0.74), false, T_BRICK, Vector2(22, 10), "brick")
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.86, 0.85, 0.83), false, T_BRICK, Vector2(22, 10), "brick")
# Stone base course so the facade has a footing instead of meeting the steps dead-on.
_add_box(building, Vector3(40, 0.8, 0.35), Vector3(0, 0.4, -3.85), Color(0.58, 0.56, 0.53), false, null, Vector2.ONE, "stone")
_add_box(building, Vector3(40, 0.8, 0.5), Vector3(0, 0.4, -3.78), Color(0.52, 0.5, 0.47), false, null, Vector2.ONE, "stone")
# Facade depth, not a flat wall: a projecting plinth, a string course between
# the window bands, a cornice under the roofline and pilasters at the bays.
_add_box(building, Vector3(40, 0.34, 0.22), Vector3(0, 0.93, -3.9), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
_add_box(building, Vector3(40, 0.28, 0.28), Vector3(0, 7.6, -3.86), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
_add_box(building, Vector3(40, 0.5, 0.5), Vector3(0, 17.7, -3.75), Color(0.56, 0.54, 0.51), true, null, Vector2.ONE, "stone")
_add_box(building, Vector3(40, 0.22, 0.2), Vector3(0, 10.9, -3.9), Color(0.58, 0.56, 0.53), true, null, Vector2.ONE, "stone")
for bay in [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3]:
_add_box(building, Vector3(0.5, 8.4, 0.24), Vector3(bay, 12.6, -3.9),
Color(0.8, 0.78, 0.76), true, T_BRICK, Vector2(0.3, 4.7), "brick")
# Entrance recess + door recess frame
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -3.9), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone")
# Door frame columns and lintel, so the entrance is framed rather than painted on.
@@ -151,9 +174,14 @@ func _build_facade_windows(parent: Node3D) -> void:
var pane_mat := Materials.window_pane()
pane.material_override = pane_mat
_window_mats.append(pane_mat)
# Sill below the frame.
# Sill below the frame and glazing bars, so the pane reads as a window
# rather than a dark hole.
_add_box(parent, Vector3(2.1, 0.12, 0.3), Vector3(x, y - 1.3, -3.86),
Color(0.55, 0.53, 0.50), false, null, Vector2.ONE, "stone")
_add_box(parent, Vector3(0.06, 2.2, 0.05), Vector3(x, y, -3.9),
Color(0.24, 0.23, 0.22), false, null, Vector2.ONE, "stone")
_add_box(parent, Vector3(1.7, 0.06, 0.05), Vector3(x, y + 0.25, -3.9),
Color(0.24, 0.23, 0.22), false, null, Vector2.ONE, "stone")
func _build_steps() -> void:
# Steps have collision so NPCs and the guard can walk up the entrance.
@@ -187,7 +215,7 @@ func _build_steps() -> void:
func _build_sidewalk() -> void:
var sidewalk := StaticBody3D.new()
sidewalk.name = "Sidewalk"
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.74, 0.74, 0.72), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.55, 0.55, 0.54), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
var col := BoxShape3D.new()
col.size = Vector3(30, 0.36, 8)
var so := sidewalk.create_shape_owner(sidewalk)
@@ -200,7 +228,12 @@ func _build_street() -> void:
street.name = "Street"
# The roadway reads darker than the sidewalk; the raw asphalt texture tinted
# white looked like more pavement.
_add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22), Color(0.52, 0.53, 0.57), false, T_ASPHALT, Vector2(30, 7), "asphalt")
# The street keeps its own material so night mode can switch to the wet
# asphalt map (glossier, with lamp reflections) and back.
_street_mat = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(30, 7))
var street_mesh := _add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22),
Color(0.62, 0.63, 0.66), false, T_ASPHALT, Vector2(30, 7), "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(-40, 41, 4):
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, 22), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
@@ -239,28 +272,32 @@ func _build_skyline_backdrop() -> void:
## band from the sidewalk.
var backdrop := Node3D.new()
backdrop.name = "SkylineBackdrop"
var mat := StandardMaterial3D.new()
mat.albedo_texture = T_SKYLINE
mat.texture_repeat = true
mat.uv1_scale = Vector3(3.0, 1.0, 1.0)
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
mat.alpha_scissor_threshold = 0.5
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
# Unshaded silhouettes read as pure black at 255-scale; lift the albedo so the
# distant skyline sits closer to a hazy daytime city.
mat.albedo_color = Color(2.6, 2.7, 3.0, 1.0)
# A shader, not a StandardMaterial3D: the panorama already has its own
# atmospheric haze baked in, and the scene fog at 200 m washed the towers out
# to a white band (Godot 4.3 has no `fog_enabled` on StandardMaterial3D, so the
# `fog_disabled` render mode does the job). Two UV repeats over the 400 m ring:
# each repeat is ~200 x 90 m, close to the 2:1 panorama aspect, so the towers
# keep their proportions instead of stretching into a wide band.
var mat := ShaderMaterial.new()
var shader := Shader.new()
shader.code = SKYLINE_SHADER
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("repeats", 2.0)
_skyline_mat = mat
var mesh := PlaneMesh.new()
# PlaneMesh defaults to a horizontal (XZ) plane; the skyline must stand up.
mesh.orientation = PlaneMesh.FACE_Z
mesh.size = Vector2(400, 60)
# 90 m tall: at the 200 m ring distance the towers subtend a skyline band
# instead of a thin strip on the horizon.
mesh.size = Vector2(400, SKYLINE_HEIGHT)
# Far enough that the skyline reads as background and its top edge stays out
# of the middle of the sky.
const DIST := 200.0
# The texture keeps its buildings in the lower part of the image, so the plane
# center sits at half its height: the building bases meet the ground.
const CENTER_Y := 30.0
const CENTER_Y := SKYLINE_HEIGHT / 2.0
var placements := [
[Vector3(0, CENTER_Y, -DIST), 0.0],
[Vector3(0, CENTER_Y, DIST), 0.0],
@@ -277,7 +314,27 @@ func _build_skyline_backdrop() -> void:
backdrop.add_child(plane)
root.add_child(backdrop)
var _skyline_mat: StandardMaterial3D = null
## Backdrop shader: unshaded, alpha-scissored, and fog-free so the panorama's own
## haze shows instead of the scene fog bleaching it.
const SKYLINE_SHADER := """
shader_type spatial;
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 float repeats = 2.0;
void fragment() {
vec4 panorama = texture(sky_texture, vec2(UV.x * repeats, UV.y));
ALBEDO = panorama.rgb * tint.rgb;
ALPHA = panorama.a * tint.a;
ALPHA_SCISSOR_THRESHOLD = 0.5;
}
"""
var _skyline_mat: ShaderMaterial = null
var _street_mat: StandardMaterial3D = null
var _phase: int = Rules.Phase.EARLY
func _build_marquee() -> void:
## Blender canopy + sign box above the door; the procedural striped awning and
@@ -712,6 +769,26 @@ func _build_crosswalk() -> void:
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 17.0), Color(0.92, 0.92, 0.92))
_add_box(root, Vector3(0.8, 0.02, 0.5), Vector3(-2.8 + i * 0.85, 0.11, 19.0), Color(0.92, 0.92, 0.92))
## Apply a shift phase: skyline panorama, dry/wet street, and night lighting.
## Called by GameManager on every phase change.
func set_phase(phase: int) -> void:
_phase = phase
var entry: Dictionary = SKYLINE_BY_PHASE.get(phase, SKYLINE_BY_PHASE[Rules.Phase.EARLY])
if _skyline_mat != null:
_skyline_mat.set_shader_parameter("sky_texture", entry["texture"])
_skyline_mat.set_shader_parameter("tint", entry["tint"])
var night: bool = phase == Rules.Phase.EVENING or phase == Rules.Phase.LATE
set_night_mode(night)
if _street_mat != null:
var wet: bool = phase == Rules.Phase.LATE
var dry: StandardMaterial3D = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(30, 7))
var damp: StandardMaterial3D = Materials.asphalt(Color(0.52, 0.55, 0.6), Vector2(30, 7), true)
var target: StandardMaterial3D = damp if wet else dry
_street_mat.albedo_texture = target.albedo_texture
_street_mat.normal_texture = target.normal_texture
_street_mat.roughness = target.roughness
_street_mat.metallic_specular = target.metallic_specular
## Switch street lamps and signage to night mode (emissive + lights on).
func set_night_mode(night: bool) -> void:
for mat in _lamp_bulb_mats: