Adopt the realism pass and refresh screenshots/docs
- 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:
1 parent
26ecb93fd4
commit
d7e523f63c
70 files changed
+4042
-101
No files matched your search
+101
-24
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user